Files
home-network-k3s-impl/bootstrap/sops/CREDENTIALS.md
T
jakman85andClaude Opus 4.8 4f3be7ef79 docs(sops): exact Cloudflare token creation steps (Zone scope, not Account)
The terse 'Zone->DNS->Edit + Zone->Read' sent the operator to the Account
permission scope. Spell out switching the scope dropdown to Zone + the exact
two Zone-scoped rows + Specific-zone resource.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-16 22:41:02 -05:00

5.0 KiB

Credentials & SOPS — operator provisioning checklist

All platform secrets are SOPS-encrypted (age), committed to git, and decrypted by Flux at reconcile (ADR-0007, R-SEC-01). Agents never handle real secret values — encryption is done by you (the operator). This file is the end-to-end flow. Templates live in secret-templates/; the master .sops.yaml is at the repo root.

Credentials are provisioned upfront so component rollout is gated only by phase/readiness, never by a missing secret.


One-time SOPS setup (do this first)

  1. Generate the age keypair (private key stays on your machine + goes to the cluster; never in git):
    ./bootstrap/sops/age-keygen.sh          # prints the PUBLIC recipient (age1…) + writes the private key
    
  2. Fill the recipient into the repo-root .sops.yaml — replace both REPLACE-ME-AGE-PUBLIC-RECIPIENT occurrences with the age1… public key from step 1. (This file governs encryption for platform/**, apps/**, catalog/**, and any secrets/**.)
  3. Install the private key on the cluster as the sops-age Secret so Flux can decrypt:
    ./bootstrap/sops/install-age-key.sh     # creates secret/sops-age in flux-system (see age-key-secret.md)
    
    The dev/prod overlays already point the platform/apps Flux Kustomizations at decryption.secretRef: sops-age.

Keep the age private key out of the repo (same discipline as the ansible-vault passphrase at ~/.config/home-k3s/ansible-vault-pass). If it leaks, rotate: new key → re-encrypt all *.sops.yaml → re-install.


Per-secret flow (repeat for each secret below)

# 1. copy the template into its component dir under its real name
cp bootstrap/sops/secret-templates/<template>.yaml.example platform/<component>/<name>.sops.yaml
# 2. fill the real value(s) in stringData:
$EDITOR platform/<component>/<name>.sops.yaml
# 3. encrypt in place (SOPS picks up the root .sops.yaml rules automatically)
sops -e -i platform/<component>/<name>.sops.yaml
# 4. wire it: uncomment the pre-added `- <name>.sops.yaml` line in platform/<component>/kustomization.yaml
# 5. verify it still builds, then commit the CIPHERTEXT
kubectl kustomize platform/<component> >/dev/null && git add -A && git commit

Secret inventory (provision all now; wire per phase)

Secret Namespace Keys Consumer Phase Template
cloudflare-api-token cert-manager api-token cert-manager DNS-01 ClusterIssuer 2 cloudflare-api-token.cert-manager.yaml.example
cloudflare-api-token external-dns api-token ExternalDNS (CF_API_TOKEN) — same token, 2nd copy 2 cloudflare-api-token.external-dns.yaml.example
grafana-admin-credentials monitoring admin-user, admin-password Grafana admin 4 grafana-admin-credentials.yaml.example
alertmanager-smtp monitoring smtp-password Alertmanager email 4 alertmanager-smtp.yaml.example
keycloak-admin-credentials keycloak admin-user, admin-password ⚠️verify chart keys; DB secret likely also needed Keycloak IdP 6 keycloak-admin-credentials.yaml.example
oauth2-proxy-credentials oauth2-proxy client-id, client-secret, cookie-secret ⚠️verify chart keys oauth2-proxy forward-auth 6 oauth2-proxy-credentials.yaml.example
pia-credentials (Phase-3 VPN ns, TBA) username, password Gluetun VPN egress (protocol ≠ TCP) — not wired yet 3 pia-credentials.yaml.example

Cloudflare token — exact creation steps (My Profile → API Tokens → Create Token → Custom token): one token serves both cert-manager (DNS-01 _acme-challenge TXT) and ExternalDNS (A/CNAME), same value in both namespace copies. In each Permissions row, the left dropdown defaults to Account — switch it to Zone (Account-scope groups like "Account DNS Settings"/"DNS Firewall"/"Registrar Domains" are the wrong scope and not needed). Set:

Scope Group Access
Zone DNS Edit
Zone Zone Read
  • Zone Resources: Include → Specific zone → keithjackson.dev.
  • Leave Client-IP/TTL filtering blank → Continue to summary → Create Token → copy the value into the api-token field of both cloudflare-api-token secret copies.

Phase-2 blockers (needed to un-suspend the Phase-2 platform tier)

  • SOPS one-time setup (age key → .sops.yaml → sops-age on cluster).
  • cloudflare-api-token provisioned + wired in both cert-manager and external-dns.
  • ACME account email set in platform/cert-manager/clusterissuers.yaml (both issuers) — non-secret; a sensible default may already be filled, confirm it.
  • Push current main to the host Gitea (Flux source), then create the dev root Kustomization to reconcile clusters/dev.

Later-phase secrets (Grafana/SMTP, Keycloak, oauth2-proxy, PIA) can be encrypted now and simply wired when their component is enabled in platform/kustomization.yaml.