Secrets
No chart contains a credential. Every one is referenced by name. This page is explains what must exist, with which keys, before the platform starts.
The secrets you create
| Secret | Namespace | Keys | Created in |
|---|---|---|---|
gen0sec-registry | Both | .dockerconfigjson | Install step 2 |
gen0sec-object-store | gen0sec-system | RUSTFS_ACCESS_KEY, RUSTFS_SECRET_KEY | Install step 2 |
gen0sec-download-proxy | gen0sec | DOWNLOAD_PROXY_API_KEY | Install step 2 |
The pull secret is needed in both namespaces. The application runs in gen0sec; the operators,
object store and cache run in gen0sec-system.
The twenty the script creates
make-db-secrets.sh composes one secret per service from the passwords the Postgres operator
generated.
| Secret | Contains | Role it uses | Endpoint |
|---|---|---|---|
g0s-db-<service> | DATABASE_URL, plus object store keys for services that use it | Its own per-service role, read-only where it only reads | The connection pooler |
gen0sec-db-migrate | DATABASE_URL | postgres, superuser | The primary, unpooled |
g0s-db-ui | DATABASE_URL, BETTER_AUTH_SECRET, object store keys | frontend | The connection pooler |
One more is optional and not created by the script: SMTP credentials for invitation and
password-reset email, referenced from services.ui.envSecrets. See
Invitations and email. A referenced Secret that does not exist
leaves the dashboard in CreateContainerConfigError.
Every service connects as its own role. The migration job needs a superuser on the primary because it
runs CREATE EXTENSION, which an owner role cannot do.
g0s-db-ui carries the dashboard's session signing secret. Re-running the script keeps the existing
value, so sessions and invite links stay valid across a re-run. Nothing else regenerates it.
Four services need no database connection and get no DATABASE_URL: the GeoIP updater, the GeoIP
database updater, the model publisher and the rules validator.
Where the database passwords come from
The Postgres operator creates a credentials secret per role it provisions, named after the role:
<role>.core.credentials.postgresql.acid.zalan.do
Underscores in the role become dashes in the secret name.
Compose each connection string from that role's password:
postgres://<role>:<password>@core-pooler.gen0sec-system.svc.cluster.local:5432/ax_prod?sslmode=require
| Consumer | Role | Endpoint |
|---|---|---|
| Each service | Its own per-service role | core-pooler |
| Migration job | postgres | core, the primary, with the on-prem marker appended |
| Dashboard | frontend | core-pooler |
The migration job's connection string goes to the primary and carries the on-prem marker,
options=-c%20gen0sec.deployment_mode%3Donprem:
postgres://postgres:<password>@core.gen0sec-system.svc.cluster.local:5432/ax_prod?sslmode=require&options=-c%20gen0sec.deployment_mode%3Donprem
Migrations read the marker to tell your database from our hosted one. Without it the seeded admin account is not created, and from v0.2.1 rule changes stop reaching agents.
The dashboard's connection string uses sslmode=no-verify. Its client library verifies the certificate
chain for sslmode=require and rejects the pooler's self-signed certificate; the Go library every
other service uses reads the same value as encrypt-without-verify.
Do not copy no-verify onto the other services. Their driver rejects it outright.
For production: use your secret manager
make-db-secrets.sh is fine for evaluation. In production, treat its output as a specification
rather than a tool, and have your secret manager produce secrets of the same shape.
Two reasons:
- The script writes plain Kubernetes secrets from operator-generated passwords. Your manager should be the source of truth, with the cluster holding copies.
- Rotation is then a manager operation rather than a re-run plus a rollout.
An external secret can template the connection string from the operator-generated password, so nothing is copied by hand:
# shape, not a working manifest: adapt to your operator
data:
- secretKey: DATABASE_URL
remoteRef:
key: g0s-auth-api-ro.core.credentials.postgresql.acid.zalan.do
property: password
# then template: postgres://<role>:{{ .password }}@core-pooler...:5432/ax_prod?sslmode=require
To see the exact list your install needs, read it out of the values file rather than from this page:
grep -oE 'secretRef:\s*\{?\s*name:\s*[a-z0-9-]+' \
$B/values/gen0sec-platform-values-onprem.yaml | sort -u
Or inspect your live installation inside your cluster.
Rotation
| Credential | How |
|---|---|
| Database role passwords | Rotate through the Postgres operator, then restart the consumers |
| Object store keys | Update both gen0sec-object-store and every g0s-db-<service> that carries them, then restart the consumers |
| Registry pull credentials | Recreate gen0sec-registry in both namespaces. Existing pods keep running; new pulls use the new value |
| Your Gen0Sec API key | Recreate gen0sec-download-proxy, then restart the relay |
| Dashboard session key | Rotating invalidates every session and every outstanding invite link. Passwords and passkeys are unaffected |
| SMTP credentials | Update the Secret, then kubectl -n gen0sec rollout restart deploy/g0s-ui |
The object store keys appear as RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY to the object store itself,
and as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to the services that read it. Same values, two
naming conventions. Rotating means updating both places.
Verifying
Nothing should be missing before you install the platform:
$KIT/scripts/preflight.sh
After install, confirm the count:
kubectl -n gen0sec get secret | wc -l # ~20 service secrets plus the ones you created
kubectl -n gen0sec-system get secret | grep credentials | wc -l # expect 6
What is never in a secret
| Where it is instead | |
|---|---|
| The registry host and namespace | The values file |
| The relay upstream URL | The values file |
| The cache connection string | The values file, deliberately with no credential in it |
If you enable authentication on the cache, remove that line from the values file and put the connection string in each service's secret instead.