fix(openbao): harden secrets backend (prune-guard, versioned policy, store scoping, egress) — Fixes #202 #231

Merged
k.renner merged 1 commits from fix/issue-202-openbao-hardening into master 2026-07-14 15:25:37 +00:00
Member

Fixes #202 — the safely-codeable subset of the OpenBao hardening bundle. Two disruptive items are left as owner decisions (below).

Done in this PR (low-risk, GitOps-applied)

1. Prune-guard the snapshot PVC (infrastructure/openbao/snapshot-pvc.yaml)
Added kustomize.toolkit.fluxcd.io/prune: disabled. This PVC holds the only supported restore artifact (the raft snapshots; the raft data PVC itself is deliberately not backed up). Now a rename/move/removal of the manifest can't delete the backup volume — matching the sibling data PVCs (home-assistant, seed/immich, seed/paperless).

2. Version-control the eso-read policy (infrastructure/openbao/policies/eso-read.hcl, new)
The policy previously existed only as a heredoc in MIGRATION-sops-openbao.md + live config, with no drift detection. Committed verbatim as the source of truth, with apply/verify commands (bao policy write / bao policy read) inline. Reference-only: OpenBao policies aren't Kubernetes resources, so nothing in-repo reconciles them — it's not wired into kustomize (kustomize build of the dir still passes).

3. Scope the ClusterSecretStore (infrastructure/post-crds/openbao-clustersecretstore.yaml)
Added spec.conditions limiting the store to the exact current consumer namespaces (12: apps, audiobookshelf, authentik, cert-manager, evcc, home-assistant, immich, influxdb, ingress, monitoring, paperless, velero). Verified against every ExternalSecret with secretStoreRef name=openbao (no ClusterExternalSecret/PushSecret exist). Closes the "any namespace can read all of homelab/*" gap.
⚠️ Maintenance tradeoff: a new consuming namespace must be added to this list first, or its ExternalSecret won't sync. Flagged in an in-file comment.

4. Egress NetworkPolicy for the openbao ns (infrastructure/network-policies/openbao-ns.yaml)
Added an egress stanza to the existing CiliumNetworkPolicy, flipping the namespace from egress-unrestricted to egress default-deny. Allows intra-ns (raft :8201 + snapshot path), kube-dns, kube-apiserver/host/remote-node (TokenReview + service_registration), world (external, e.g. optional OIDC), and ICMP — mirroring the authentik-ns house pattern. Net effect: blocks lateral in-cluster egress from the secrets backend without re-opening outbound.

All four validated: kubectl kustomize builds clean for network-policies, openbao, and post-crds.

Verify after merge (live-sensitive — items 3 & 4)

  • ESO still syncs: kubectl get externalsecret -A all SecretSynced.
  • Snapshot CronJob still logs in + writes: next run of openbao-raft-snapshot succeeds.
  • OpenBao auth path healthy (item 4 restricts egress to the API server).

Left as owner decisions (disruptive — NOT in this PR)

  • Enable listener TLS / mTLS (tls_disable = 1): requires issuing/mounting certs, switching the store server: + CronJob BAO_ADDR to https, and re-pointing auth/kubernetes/config — live-sensitive, not doable non-disruptively. Proposed; needs a maintenance window.
  • Per-app KV path re-segregation: splitting homelab/* into per-app paths with scoped policies/roles (vs. today's single eso-read reading everything) is a data + policy migration. The store-scoping in item 3 is the non-disruptive partial mitigation; full segregation needs an owner call on approach.

Single-replica raft is a documented/accepted tradeoff (snapshot-compensated) and is intentionally untouched.

Fixes #202 — the safely-codeable subset of the OpenBao hardening bundle. Two disruptive items are left as owner decisions (below). ## Done in this PR (low-risk, GitOps-applied) **1. Prune-guard the snapshot PVC** (`infrastructure/openbao/snapshot-pvc.yaml`) Added `kustomize.toolkit.fluxcd.io/prune: disabled`. This PVC holds the *only* supported restore artifact (the raft snapshots; the raft data PVC itself is deliberately not backed up). Now a rename/move/removal of the manifest can't delete the backup volume — matching the sibling data PVCs (home-assistant, seed/immich, seed/paperless). **2. Version-control the `eso-read` policy** (`infrastructure/openbao/policies/eso-read.hcl`, new) The policy previously existed only as a heredoc in `MIGRATION-sops-openbao.md` + live config, with no drift detection. Committed verbatim as the source of truth, with apply/verify commands (`bao policy write` / `bao policy read`) inline. Reference-only: OpenBao policies aren't Kubernetes resources, so nothing in-repo reconciles them — it's not wired into kustomize (kustomize build of the dir still passes). **3. Scope the ClusterSecretStore** (`infrastructure/post-crds/openbao-clustersecretstore.yaml`) Added `spec.conditions` limiting the store to the exact current consumer namespaces (12: apps, audiobookshelf, authentik, cert-manager, evcc, home-assistant, immich, influxdb, ingress, monitoring, paperless, velero). Verified against every ExternalSecret with `secretStoreRef name=openbao` (no ClusterExternalSecret/PushSecret exist). Closes the "any namespace can read all of `homelab/*`" gap. ⚠️ Maintenance tradeoff: a new consuming namespace must be added to this list first, or its ExternalSecret won't sync. Flagged in an in-file comment. **4. Egress NetworkPolicy for the openbao ns** (`infrastructure/network-policies/openbao-ns.yaml`) Added an `egress` stanza to the existing CiliumNetworkPolicy, flipping the namespace from egress-unrestricted to egress default-deny. Allows intra-ns (raft :8201 + snapshot path), kube-dns, kube-apiserver/host/remote-node (TokenReview + service_registration), `world` (external, e.g. optional OIDC), and ICMP — mirroring the `authentik-ns` house pattern. Net effect: blocks lateral in-cluster egress from the secrets backend without re-opening outbound. All four validated: `kubectl kustomize` builds clean for network-policies, openbao, and post-crds. ## Verify after merge (live-sensitive — items 3 & 4) - ESO still syncs: `kubectl get externalsecret -A` all `SecretSynced`. - Snapshot CronJob still logs in + writes: next run of `openbao-raft-snapshot` succeeds. - OpenBao auth path healthy (item 4 restricts egress to the API server). ## Left as owner decisions (disruptive — NOT in this PR) - **Enable listener TLS / mTLS** (`tls_disable = 1`): requires issuing/mounting certs, switching the store `server:` + CronJob `BAO_ADDR` to https, and re-pointing `auth/kubernetes/config` — live-sensitive, not doable non-disruptively. Proposed; needs a maintenance window. - **Per-app KV path re-segregation**: splitting `homelab/*` into per-app paths with scoped policies/roles (vs. today's single `eso-read` reading everything) is a data + policy migration. The store-scoping in item 3 is the non-disruptive partial mitigation; full segregation needs an owner call on approach. Single-replica raft is a documented/accepted tradeoff (snapshot-compensated) and is intentionally untouched.
claude-bot added 1 commit 2026-07-14 10:09:23 +00:00
Addresses the safely-codeable items of the #202 hardening bundle:

- snapshot-pvc: add kustomize.toolkit.fluxcd.io/prune: disabled so the sole
  supported restore artifact (raft snapshots PVC) survives a manifest
  rename/move, matching sibling data PVCs.
- policies/eso-read.hcl: version-control the eso-read policy that only lived
  as a heredoc in the migration doc + live config; reference-only (OpenBao
  policies aren't Flux-reconciled), with apply/verify commands inline.
- clustersecretstore: add conditions restricting the store to the exact
  current consumer namespaces, closing the "any namespace can read all of
  homelab/*" gap.
- network-policies/openbao-ns: add an egress stanza (default-deny egress,
  allowing intra-ns, kube-dns, kube-apiserver/host, world, ICMP) to block
  lateral in-cluster egress from the secrets backend.

Listener TLS/mTLS and per-app KV path re-segregation are the disruptive
remaining items and are raised as owner decisions on #202. Single-replica
raft is a documented/accepted tradeoff, not touched.

Fixes #202

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
k.renner merged commit 0a53419cd6 into master 2026-07-14 15:25:37 +00:00
Sign in to join this conversation.