goharbor/harbor · error · Exception

Cert File {} should be owned by user with uid 10000 or reada

Error message

Cert File {} should be owned by user with uid 10000 or readable by others

What it means

Thrown by validate() in make/photon/prepare/utils/configs.py when the registry custom CA bundle is owned by uid 10000 (DEFAULT_UID, Harbor's runtime user) but the owner-read permission bit is missing (owner_can_read(st_mode) is false). A file owned by the Harbor uid that it cannot read is treated as a misconfiguration and blocks prepare, since registry/core would fail to load it.

Source

Thrown at make/photon/prepare/utils/configs.py:73

        if storage_provider_config == "":
            raise Exception(
                "Error: no provider configurations are provided for provider %s" % storage_provider_name)
    # ca_bundle validate
    if conf.get('registry_custom_ca_bundle_path'):
        registry_custom_ca_bundle_path = conf.get('registry_custom_ca_bundle_path') or ''
        if registry_custom_ca_bundle_path.startswith('/data/'):
            ca_bundle_host_path = registry_custom_ca_bundle_path
        else:
            ca_bundle_host_path = os.path.join(host_root_dir, registry_custom_ca_bundle_path.lstrip('/'))
        try:
            uid = os.stat(ca_bundle_host_path).st_uid
            st_mode = os.stat(ca_bundle_host_path).st_mode
        except Exception as e:
            logging.error(e)
            raise Exception('Can not get file info')
        err_msg = 'Cert File {} should be owned by user with uid 10000 or readable by others'.format(registry_custom_ca_bundle_path)
        if uid == DEFAULT_UID and not owner_can_read(st_mode):
            raise Exception(err_msg)
        if uid != DEFAULT_UID and not other_can_read(st_mode):
            raise Exception(err_msg)

    # TODO:
    # If user enable trust cert dir, need check if the files in this dir is readable.

    if conf.get('trace'):
        conf['trace'].validate()

    if conf.get('purge_upload'):
        conf['purge_upload'].validate()

    if conf.get('cache'):
        conf['cache'].validate()

    if conf.get('core'):
        conf['core'].validate()

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. Restore the owner-read bit: chmod u+r <ca_bundle> (or chmod 0400/0444)
  2. Confirm with: stat -c '%u %a' <ca_bundle> # expect 10000 and a mode with owner r
  3. Re-run ./install.sh
  4. If the file is deliberately root-owned, see the sibling check (error 30) and make it world-readable instead

Example fix

# on the Harbor host (before)
$ stat -c '%u %a' /data/secret/root-ca.pem
10000 200

# after
$ chmod 0400 /data/secret/root-ca.pem
$ stat -c '%u %a' /data/secret/root-ca.pem
10000 400
Defensive patterns

Strategy: validation

Validate before calling

import os, stat as st
p = cfg.get('storage_service', {}).get('ca_bundle')
if p and os.path.isfile(p):
    s = os.stat(p)
    if s.st_uid == 10000 and not (s.st_mode & st.S_IRUSR):
        raise SystemExit('ca_bundle owned by 10000 lacks owner read: chmod u+r %s' % p)

Prevention

When it happens

Trigger: harbor.yml sets storage_service.ca_bundle to a file whose stat shows st_uid == 10000 and whose mode lacks 0400 for the owner — e.g. mode 0200, 0000, or 0260 after restrictive chmods.

Common situations: Security hardening scripts that strip read bits; files restored from backups with odd modes; admins chowning to 10000 but not adjusting mode.

Related errors


AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16). Data as JSON: /api/errors/774e0bad6d06888e. Report an issue: GitHub.