TazLab K8s: External Secrets Detail
Level 3 (Detail) โ ESO configuration, ClusterSecretStore, and all ExternalSecret manifests.
Concept
External Secrets Operator (ESO) bridges external secret stores with native Kubernetes Secrets. All cluster secrets are served by HashiCorp Vault (tazlab-secrets-vault). Infisical (tazlab-secrets) is retained as a legacy store deployed by Terraform for engine-level backward compat only โ zero ExternalSecrets reference it.
ClusterSecretStore
Two stores operating in parallel (two-store model):
| Store | Backend | Status | Managed by |
|---|---|---|---|
tazlab-secrets-vault | HashiCorp Vault (Hetzner) | โ Primary | Flux (GitOps, tazlab-k8s/) |
tazlab-secrets | Infisical EU (legacy) | ๐ Empty creds, no consumers | Terraform (engine layer) |
ExternalSecret Inventory
All secrets stored at secret/data/tazlab-k8s/static/<domain>/<consumer>/<KEY> in Vault KV v2. remoteRef.property: value on all entries. 22 ExternalSecrets total.
cert-manager
File: infrastructure/configs/cert-manager/cloudflare-external-secret.yaml
| Output Secret | Namespace | Key | Vault Path |
|---|---|---|---|
cloudflare-api-token | cert-manager | api-token | tazlab-k8s/static/infra/cloudflare-ddns/CLOUDFLARE_API_TOKEN |
Wildcard TLS
File: infrastructure/configs/wildcard-tls/external-secret.yaml
| Output Secret | Namespace | Template Type | Vault Path |
|---|---|---|---|
wildcard-tls | hugo-blog | kubernetes.io/tls | tazlab-k8s/static/tls/wildcard/WILDCARD_* |
The wildcard TLS is also replicated via ExternalSecret in namespaces:
dexโinfrastructure/instances/dex/wildcard-tls-secret.yamlauthโinfrastructure/auth/oauth2-proxy/wildcard-tls-secret.yamllonghorn-systemโinfrastructure/instances/longhorn/ingress.yaml
tazlab-db (S3)
File: infrastructure/configs/tazlab-db/s3-external-secret.yaml
| Output Secret | Namespace | Template | Vault Path |
|---|---|---|---|
s3-backrest-creds | tazlab-db | Generates s3.conf | tazlab-k8s/static/storage/tazlab-db/AWS_* |
Uses a template to generate the PGBackrest configuration file format:
template:
data:
s3.conf: |
[global]
repo1-s3-key={{ .AWS_ACCESS_KEY_ID }}
repo1-s3-key-secret={{ .AWS_SECRET_ACCESS_KEY }}
GitHub (Flux Automation)
File: infrastructure/configs/github-external-secret.yaml
| Output Secret | Namespace | Key | Vault Path |
|---|---|---|---|
github-api-token | flux-system | github_token | tazlab-k8s/static/infra/github/GITHUB_TOKEN |
Used by Flux ImageUpdateAutomation to commit image tag updates back to the tazlab-k8s repository.
OAuth2 Proxy
File: infrastructure/auth/oauth2-proxy/external-secret.yaml
| Output Secret | Namespace | Keys | Vault Path |
|---|---|---|---|
oauth2-proxy-secrets | auth | OAUTH2_PROXY_CLIENT_SECRET, OAUTH2_PROXY_COOKIE_SECRET | tazlab-k8s/static/auth/oauth2-proxy/OAUTH2_* |
Dex
File: infrastructure/configs/dex/ or infrastructure/instances/dex/wildcard-tls-secret.yaml
Dex uses dex-rendered-config Secret combining ConfigMap template with OIDC credentials from Vault.
OpenClaw
File: infrastructure/configs/ai-agents/
| Output Secret | Namespace | Keys | Vault Path |
|---|
Tailscale Operator
File: infrastructure/tailscale/externalsecret.yaml
| Output Secret | Namespace | Keys | Vault Path |
|---|---|---|---|
tailscale-operator-oauth | tailscale | TAILSCALE_OPERATOR_CLIENT_ID, TAILSCALE_OPERATOR_CLIENT_SECRET | tazlab-k8s/static/infra/tailscale/TAILSCALE_OPERATOR_* |
Note: This ExternalSecret was added after migration followup (12). Before that, Tailscale OAuth was seeded as a plain K8s Secret by Terraform.
Grafana Bootstrap
File: infrastructure/operators/monitoring/flux-secret-sync.yaml
| Output Secret | Namespace | Key | Vault Path |
|---|---|---|---|
grafana-bootstrap-secret | flux-system | GRAFANA_DB_PASSWORD | tazlab-k8s/static/monitoring/grafana/GRAFANA_DB_PASSWORD |
Refresh Policy
All ExternalSecrets use refreshInterval: 1h except:
s3-backrest-creds:24h(S3 credentials change rarely)cluster-varsConfigMap: static, provisioned once by ephemeral-castle
DAG Position
bridge (Level 1, creates ClusterIssuer/IngressClass)
โ configs (Level 2, creates all ExternalSecrets)
โ instances/apps (Level 3, consume the resulting Kubernetes Secrets)
Design Rules
- Never add inline secret values to manifests โ always use ExternalSecret
- Each ExternalSecret should have
creationPolicy: Ownerunless explicitly sharing across namespaces - Use
templatefor format conversion (e.g., s3.conf generation) - Wildcard TLS uses
kubernetes.io/tlstemplate type for proper secret format - Run
tests/verify_manifest_purity.shafter any manifest change
Future Direction
The Vault Agent Injector will progressively replace ESO for dynamic-capable workload secrets (Phase 2 of 15-tazlab-k8s-vault-dynamic-secrets-operator). ESO will remain for bootstrap/infrastructure-level static secrets.
See Also
- Parent topic: Secrets Mapping
- Sibling details: cert-manager Detail, tazlab-db Detail
- Reference: ExternalSecret Example
- Vault entity: Hashicorp Vault