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.
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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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-readpolicy (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.conditionslimiting 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 withsecretStoreRef name=openbao(no ClusterExternalSecret/PushSecret exist). Closes the "any namespace can read all ofhomelab/*" 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
egressstanza 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 theauthentik-nshouse pattern. Net effect: blocks lateral in-cluster egress from the secrets backend without re-opening outbound.All four validated:
kubectl kustomizebuilds clean for network-policies, openbao, and post-crds.Verify after merge (live-sensitive — items 3 & 4)
kubectl get externalsecret -AallSecretSynced.openbao-raft-snapshotsucceeds.Left as owner decisions (disruptive — NOT in this PR)
tls_disable = 1): requires issuing/mounting certs, switching the storeserver:+ CronJobBAO_ADDRto https, and re-pointingauth/kubernetes/config— live-sensitive, not doable non-disruptively. Proposed; needs a maintenance window.homelab/*into per-app paths with scoped policies/roles (vs. today's singleeso-readreading 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.