Skip to content

Grafana's two secrets, and where they live

grafana-auth carries admin-user and admin-password. It is a SealedSecret next to the chart that consumes it, and it is the only copy: Grafana mounts it, and terraform/grafana-config reads it back out of the cluster at apply time.

grafana-oidc carries client_id and client_secret for the authentik client. It is not sealed — terraform/authentik-config mints the client, writes the credentials to OpenBao at authentik/observability/grafana, and an ExternalSecret pulls them into the namespace. That is the right split: the admin password is chosen by an operator, while the OIDC secret is generated by the system that owns the client, so nobody has to see it.

The two live at different mount paths — /etc/secrets/auth and /etc/secrets/oidc — and grafana.ini reads each with $__file{}.

Sealing is offline — the pinned key pair lives in secrets/sealed-secrets.yaml, so no cluster access is needed to produce the ciphertext:

sops -d --extract '["tls_crt"]' secrets/sealed-secrets.yaml > /tmp/sealing.crt
kubeseal --cert /tmp/sealing.crt --format yaml < /tmp/grafana-auth.yaml \
  > flux/apps/observability/grafana/auth-sealed-secret.yaml

Write the plaintext input under /tmp, never in the working tree — nothing in .gitignore would catch it, and a rule broad enough to catch it would also shadow the sealed output.

Rotating the admin password is one resealed file. The tofu root picks the new value up on its next apply because it never stored the old one.