Files
infrastructure/servers/legion/k8s/external-secrets/cluster-secret-store.yaml
T
Will Anderson 93358505fc Harden prod: security, autoscaling, observability, BuildKit CI
Security:
- Drop ALL capabilities, enforce non-root, RuntimeDefault seccomp on
  neuron-mcp, neuron-rest, neuron-marketing pods
- Add startup probes (150s window for JVM) so liveness doesn't fire early
- Replace docker-sock hostPath with BuildKit rootless TCP endpoint
  (moby/buildkit:v0.19.0-rootless) — removes node root access from CI
- Document full ESO AppRole migration path in cluster-secret-store.yaml

Autoscaling & availability:
- HPAs on mcp (1–6), rest (1–4), marketing (2–8) at 65–70% CPU
- PodDisruptionBudgets (minAvailable: 1) on all three services
- NetworkPolicy: default-deny-all in neuron-prod, explicit allow rules
  for Traefik ingress, intra-namespace, and egress to DNS/platform/vault

Observability:
- ServiceMonitors for mcp, rest, marketing (cross-namespace enabled in
  kube-prometheus-stack with serviceMonitorSelectorNilUsesHelmValues:false)
- PrometheusRules: high error rate, high latency, crash loops, replica
  shortage, Postgres down/connections, backup failure, backup staleness

Chart version pinning:
- kube-prometheus-stack, loki, tempo, redis, alloy, postgres — all pinned
  to major-version ranges to block silent breaking upgrades

Backup hardening:
- restic:latest → restic:0.17.3 (deterministic image)
- Weekly backup-verify CronJob: restores latest snapshot and validates
  SQL dump structure (≥5 CREATE TABLE, pg_dump header check)

ArgoCD:
- neuron-prod AppProject: scopes deploys to neuron-prod + platform ns,
  blacklists ClusterRole/ClusterRoleBinding/Namespace creation,
  automated sync window 2–6am UTC, manual always allowed
2026-04-25 22:54:18 -05:00

70 lines
2.8 KiB
YAML

---
# ClusterSecretStore — connects ESO to Vault
#
# CURRENT STATE: token auth (root token). This works but the root token
# has no expiry and full Vault access — it's a single credential that
# compromises everything if leaked.
#
# MIGRATION TO APPROLE (do this before GCP migration):
# ─────────────────────────────────────────────────────
# 1. Enable AppRole on Vault (if not already):
# vault auth enable approle
#
# 2. Create a policy scoped to only the paths ESO reads:
# vault policy write eso-read - <<EOF
# path "secret/data/neuron-technologies/*" { capabilities = ["read"] }
# path "secret/data/r2" { capabilities = ["read"] }
# path "secret/data/slack" { capabilities = ["read"] }
# path "secret/data/legion-db" { capabilities = ["read"] }
# EOF
#
# 3. Create the AppRole:
# vault write auth/approle/role/eso \
# token_policies="eso-read" \
# token_ttl=1h \
# token_max_ttl=4h \
# secret_id_ttl=0 # no expiry for the secretId
#
# 4. Get the roleId and secretId:
# vault read auth/approle/role/eso/role-id → ROLE_ID
# vault write -f auth/approle/role/eso/secret-id → SECRET_ID
#
# 5. Create the k8s secret (one-time bootstrap, same as the root token was):
# kubectl create secret generic vault-approle \
# --namespace external-secrets \
# --from-literal=roleId=<ROLE_ID> \
# --from-literal=secretId=<SECRET_ID>
#
# 6. Flip this file to the appRole stanza below, commit, push.
# ESO will hot-reload when the manifest changes.
#
# 7. Delete the old vault-token secret:
# kubectl delete secret vault-token -n external-secrets
# ─────────────────────────────────────────────────────
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault
spec:
provider:
vault:
server: "https://vault.neuralplatform.ai"
path: "secret"
version: "v2"
auth:
# ── Current: root token auth ──────────────────────────────────────────
# TODO: replace with appRole stanza once AppRole is bootstrapped (see above).
tokenSecretRef:
name: vault-token
namespace: external-secrets
key: token
# ── Target: AppRole auth (uncomment after bootstrap) ──────────────────
# appRole:
# path: "approle"
# roleId: "eso"
# secretRef:
# name: vault-approle
# namespace: external-secrets
# key: secretId