Files
homelab/k8s/security/iam/vault-values.yaml
T

174 lines
7.9 KiB
YAML

# 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.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" {
max_parallel = 128
s3_session_timeout = "5s" endpoint = "http://minio.storage.svc.cluster.local"
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.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.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