Repository navigation
[harbor] Default secretKey "not-a-secure-key" leaves every stored credential decryptable #2736
Description
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 passesharborAdminPassword,externalURL, redis password, S3 settings, etc., but never setssecretKeyorexistingSecretSecretKey.packages/system/harbor/values.yaml— the system-chart default values don't overridesecretKeyeither.packages/system/harbor/charts/harbor/templates/core/core-dpl.yaml:206—secretKeyis 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:
- A namespace-scoped insider with read access to the harbor tenant namespace who can dump the
harbor-db-appPostgres Secret andpg_dumpthe database. - 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.
- 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