TLS and Certificates¶
Wildcard Cloudflare Origin CA certificates for the shared Gateway.
Three zones: *.unipuka.app, *.mansety.com, *.us-academy.net.
Architecture¶
Cloudflare Dashboard (Origin Server -> Create Certificate)
↓ (CF Origin CA issues wildcard cert, 15yr)
Doppler base CF_ORIGIN_CERT_<ZONE> / CF_ORIGIN_KEY_<ZONE>
↓ (ESO ClusterSecretStore + ExternalSecret, refreshInterval=1h)
K8s Secret type=kubernetes.io/tls in gateway ns
↓ (Gateway listener TLS secretRef)
Shared Gateway (Cilium Gateway API, BD-3.7)
↓ (TLS termination at origin)
Cloudflare Edge (re-encrypts browser<->CF with edge cert)
CF terminates browser TLS with an auto-managed edge cert. CF re-encrypts to the origin (our Gateway) using the Origin CA cert. The Origin CA cert is NOT a public CA cert - it is only trusted by Cloudflare.
Zone to Secret mapping¶
| Zone | Doppler keys | K8s Secret | Gateway listener |
|---|---|---|---|
*.unipuka.app |
CF_ORIGIN_CERT/KEY_UNIPUKA_APP |
tls-unipuka-app |
argocd, grafana, oep-stg api/ws |
*.mansety.com |
CF_ORIGIN_CERT/KEY_MANSETY_COM |
tls-mansety-com |
mansety-prd api/ws |
*.us-academy.net |
CF_ORIGIN_CERT/KEY_US_ACADEMY_NET |
tls-us-academy-net |
us-prd api/ws |
Rotation runbook¶
Certs are valid 15 years. Rotate only on: private key compromise, or proactively before expiry.
# 1. Reissue in CF dashboard: SSL/TLS -> Origin Server -> existing cert -> Revoke + reissue
# Download new cert + key PEMs.
# 2. Upload to Doppler (example for unipuka.app zone)
doppler secrets set --project oep --config base --silent \
"CF_ORIGIN_CERT_UNIPUKA_APP=$(cat new-unipuka-app.cert.pem)" \
"CF_ORIGIN_KEY_UNIPUKA_APP=$(cat new-unipuka-app.key.pem)"
# 3. ESO syncs within refreshInterval (1h). Force immediate:
kubectl annotate externalsecret tls-unipuka-app -n gateway \
force-sync=$(date +%s) --overwrite
# 4. Verify new cert loaded
kubectl -n gateway get secret tls-unipuka-app \
-o jsonpath='{.data.tls\.crt}' | base64 -d \
| openssl x509 -noout -dates -ext subjectAltName
# 5. Shred local PEM files
shred -uz new-unipuka-app.cert.pem new-unipuka-app.key.pem
Also see rotate-secrets.md for the full BD-3 cert rotation flow.
Debugging¶
ExternalSecret not Ready¶
kubectl -n gateway describe externalsecret tls-unipuka-app
# Check: Status.Conditions[type=Ready].Message
# Common: ClusterSecretStore not Valid, key name wrong, Doppler API unreachable
Gateway listener Programmed=False¶
kubectl -n gateway describe gateway shared
# Look for: Listeners[].Conditions[type=ResolvedRefs].Message
# Common: TLS secret not found, wrong namespace, secret type not kubernetes.io/tls