# k8s/talos-iam/vault-values.yaml # HashiCorp Vault — secrets backend for the homelab. # Stores OIDC client secrets, TLS certs, and any other sensitive values. # Accessed via the `talos` CLI (talos-cli/) which wraps `vault kv get/put`. # # Storage backend: MinIO S3 (minio.storage.svc.cluster.local) — no extra PVC. # Auto-unseal: postStart hook reads unseal keys from vault-unseal-keys Secret # (written by setup_vault.sh after operator init; operator must run that script # once after first install to initialize and store the keys). # ── Global ──────────────────────────────────────────────────────────────────── # tlsDisable: true — TLS terminated at the nginx ingress (vault.riotpiao.homelab.com) # or at port-forward. In-cluster traffic to Vault is plain HTTP; this is acceptable # because all clients are on the pod network (not crossing node boundaries). global: enabled: true tlsDisable: true # ── Agent Injector ──────────────────────────────────────────────────────────── # The injector mutates pods to sidecar Vault Agent for automatic secret injection. # Not used here — secrets are fetched explicitly via the talos CLI. # Enabling it would add a webhook that intercepts all pod creates cluster-wide, # which is unnecessary overhead for a homelab with manual secret management. injector: enabled: false server: replicas: 1 annotations: secret.reloader.stakater.com/reload: "vault-unseal-keys" resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi # ── Scheduling ───────────────────────────────────────────────────────────── # Tolerate cp-1 so Vault can run there if worker-1 is down. # Prefer worker-1 under normal conditions (keeps Vault off the same node as etcd). tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: node-role.kubernetes.io/worker operator: Exists # ── Environment variables ─────────────────────────────────────────────────── # extraEnvironmentVars: non-secret config passed directly. extraEnvironmentVars: VAULT_LOG_LEVEL: info # extraSecretEnvironmentVars: pulls values from K8s Secrets into env vars. # vault-minio-creds is created by the helmfile presync hook from MINIO_ROOT_USER/PASSWORD. # vault-unseal-keys is a placeholder created at first deploy; setup_vault.sh # overwrites it with real unseal keys after `vault operator init`. # Vault reads the keys from env on every pod start and the postStart hook unseals. extraSecretEnvironmentVars: - envName: AWS_ACCESS_KEY_ID secretName: vault-minio-creds secretKey: access_key - envName: AWS_SECRET_ACCESS_KEY secretName: vault-minio-creds secretKey: secret_key - envName: VAULT_UNSEAL_KEY_1 secretName: vault-unseal-keys secretKey: key1 - envName: VAULT_UNSEAL_KEY_2 secretName: vault-unseal-keys secretKey: key2 - envName: VAULT_UNSEAL_KEY_3 secretName: vault-unseal-keys secretKey: key3 # ── Auto-unseal ───────────────────────────────────────────────────────────── # Vault starts sealed after every pod restart and can't serve requests until # unsealed. postStart runs immediately after the container starts, sleeps 5s # to let the Vault process bind its port, then feeds the unseal keys one by one. # `|| true` prevents the hook from failing if a key was already used (idempotent). # 3-of-5 Shamir unseal is the default — we stored all 3 used keys in the Secret. postStart: - /bin/sh - -c - | sleep 5 vault operator unseal "$VAULT_UNSEAL_KEY_1" || true vault operator unseal "$VAULT_UNSEAL_KEY_2" || true vault operator unseal "$VAULT_UNSEAL_KEY_3" || true # ── Vault config (HCL) ────────────────────────────────────────────────────── standalone: enabled: true config: | ui = false # UI served via Vault's own HTTP; enabled below via ui: enabled: true listener "tcp" { address = "0.0.0.0:8200" tls_disable = "true" # No auth in front of Vault's metrics endpoint — acceptable since all # Prometheus scrape traffic stays on the pod network (not exposed via ingress). telemetry { unauthenticated_metrics_access = "true" } } telemetry { prometheus_retention_time = "30s" disable_hostname = true } # S3 storage backend pointing at the in-cluster MinIO service. # AWS_ env vars (from vault-minio-creds Secret) supply the credentials. # s3_force_path_style: MinIO uses path-style URLs (not virtual-hosted). # disable_ssl: MinIO in this cluster has no TLS. storage "s3" { endpoint = "http://minio.storage.svc.cluster.local:9000" bucket = "vault" region = "us-east-1" s3_force_path_style = "true" disable_ssl = "true" } # api_addr: the address other Vault nodes (or HA standbys) use to reach # this node. Single-node standalone, but Vault requires it to be set. api_addr = "http://vault.storage.svc.cluster.local:8200" # ── Service ───────────────────────────────────────────────────────────────── # NodePort 32171 — fallback for direct node access during bootstrap before # the ingress is up. Normal access is via nginx ingress (vault.riotpiao.homelab.com). service: type: NodePort port: 8200 nodePort: 32171 # ── Persistence ───────────────────────────────────────────────────────────── # No PVC — all Vault state (secrets, policies, tokens) is stored in MinIO S3. # This means Vault survives node loss as long as MinIO is healthy. dataStorage: enabled: false auditStorage: enabled: false # ── UI ──────────────────────────────────────────────────────────────────────── # Vault's web UI is used for the OIDC browser login flow (Vault as an OIDC # provider, if configured) and for manual operator inspection. # Accessible at http://vault.riotpiao.homelab.com or via port-forward. ui: enabled: true # ── Metrics ─────────────────────────────────────────────────────────────────── # vault_core_unsealed is the availability signal (0 after a restart until the # postStart hook above finishes unsealing). Pairs with the telemetry{} stanzas # in standalone.config above, which actually turn the /v1/sys/metrics endpoint on. serverTelemetry: serviceMonitor: enabled: true selectors: {} interval: 30s scrapeTimeout: 10s