Skip to content

[harbor] Default secretKey "not-a-secure-key" leaves every stored credential decryptable #2736

Open

Description

@myasnikovdaniil

Summary

The cozystack harbor wrapper never overrides the upstream chart's secretKey value, so every Harbor instance ships with the literal default key "not-a-secure-key" — the symmetric key Harbor uses to encrypt every stored credential in its PostgreSQL database (replication passwords, robot tokens, OIDC client secrets, scanner credentials, etc.).

This makes every encrypted credential in the Harbor DB effectively plaintext, since the key is a public string baked into a public Helm chart.

Evidence

  • packages/system/harbor/charts/harbor/values.yaml:377 — upstream default value:
    # The secret key used for encryption. Must be a string of 16 chars.
    secretKey: "not-a-secure-key"
    # If using existingSecretSecretKey, the key must be secretKey
    existingSecretSecretKey: ""
  • packages/system/harbor/charts/harbor/templates/core/core-secret.yaml:11-13 — the value flows verbatim into the runtime Secret.
  • packages/apps/harbor/templates/harbor.yaml — the cozystack wrapper passes harborAdminPassword, externalURL, redis password, S3 settings, etc., but never sets secretKey or existingSecretSecretKey.
  • packages/system/harbor/values.yaml — the system-chart default values don't override secretKey either.
  • packages/system/harbor/charts/harbor/templates/core/core-dpl.yaml:206 — secretKey is mounted into core as the encryption key.

A grep across the harbor source tree confirms secretKey only appears in the upstream chart files; cozystack does not template it anywhere.

Threat model

Harbor uses secretKey (CORE_SECRET / harbor_core key) as the AES key for pkg/encrypt to encrypt every credential it persists in its Postgres database — replication endpoint credentials, robot account tokens, proxy-cache upstream creds, OIDC client secrets, scanner registration tokens, notification webhook tokens, etc.

Who attacks:

  1. A namespace-scoped insider with read access to the harbor tenant namespace who can dump the harbor-db-app Postgres Secret and pg_dump the database.
  2. A backup-store attacker (anyone reading CNPG WAL/base-backups in the bucket) gets the same outcome — even without cluster access — because the key is public.
  3. A second-tenant attacker who lands a pod in another tenant namespace and pulls/pushes through Harbor's replication endpoints can request that Harbor reveal stored credentials.

Impact: the entire upstream/downstream replication credential set, robot secrets, and OIDC client secrets — i.e., a supply-chain pivot to push tampered images into any registry Harbor was configured to replicate to.

Suggested fix

Generate a stable per-release 16-character key in packages/apps/harbor/templates/harbor.yaml exactly the way the wrapper already handles admin-password and redis-password, and pass it down. Sketch:

{{- $secretKey := randAlphaNum 16 }}
{{- if $existingSecret }}
  {{- if hasKey $existingSecret.data "secret-key" }}
    {{- $secretKey = index $existingSecret.data "secret-key" | b64dec }}
  {{- end }}
{{- end }}
...
stringData:
  secret-key: {{ $secretKey | quote }}
...
values:
  harbor:
    secretKey: {{ $secretKey | quote }}

The lookup pattern is critical — rotating secretKey invalidates every existing encrypted credential in the database, so the wrapper must persist it, not regenerate on each reconcile.

If existing deployments are already running with the default key, a migration job is needed: re-encrypt all rows in robot, replication_policy, scanner_registration, oidc_user, etc., under the new key. Do not just swap the key — that bricks Harbor.

Environment

  • Cozystack: master @ 09edf16
  • Harbor chart: 1.18.2, appVersion 2.14.2

Activity

  1. added
    area/storageIssues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor)
    triage/acceptedIndicates an issue is ready to be actively worked on
    priority/critical-urgentHighest priority. Must be actively worked on as someone's top priority right now
    on Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area/storageIssues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor)kind/bugCategorizes issue or PR as related to a bugpriority/critical-urgentHighest priority. Must be actively worked on as someone's top priority right nowsecurity/criticalCritical security vulnerabilitytriage/acceptedIndicates an issue is ready to be actively worked on

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions