Skip to content

Commit f1bcdac

Browse files
committed
openbao-bootstrap: reproducible internal-config bootstrap (P4c)
Suspended CronJob + ConfigMap that idempotently re-asserts OpenBao's structural config (KV v2 mdapi mount, kubernetes auth method+config, policies admin/eso-read/mirror/archive-read/snapshot-read, and the five k8s-auth roles) so rebuilding OpenBao from the age secret-export (P3b) rather than a raft snapshot (P3a) no longer leaves a bare server that the ClusterSecretStore and mirror cannot authenticate against. Two token paths: steady-state drift re-assert via k8s-auth role=admin (SA openbao-admin); disaster recovery on a fresh raft via the optional out-of-band Secret openbao-bootstrap-token (fresh init root token, never in git). Trigger on demand: kubectl -n openbao create job --from=cronjob/openbao-bootstrap openbao-bootstrap-manual Also caps KV max_versions at 10 to bound hourly-mirror version churn.
1 parent f5246d1 commit f1bcdac

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

openbao-bootstrap/fleet.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# OpenBao internal-config bootstrap — makes the still-imperative structural config
2+
# (KV v2 mount, kubernetes auth method + config, policies, k8s-auth roles)
3+
# reproducible. WITHOUT this, restoring OpenBao from the P3b age secret-export
4+
# (rather than a P3a raft snapshot) leaves a bare server: no mount, no auth, no
5+
# policies/roles -> the `openbao` ClusterSecretStore and the mirror cannot
6+
# authenticate until the config is hand-rebuilt.
7+
#
8+
# Shipped as a SUSPENDED CronJob (same idiom as openbao-mirror): GitOps-mutable,
9+
# never auto-fires. Run on demand / after a config change with:
10+
# kubectl -n openbao create job --from=cronjob/openbao-bootstrap openbao-bootstrap-manual
11+
#
12+
# Token: idempotent re-assert (drift correction) authenticates via k8s-auth
13+
# role=admin (SA openbao-admin). Disaster recovery on a FRESH raft (admin role
14+
# doesn't exist yet) supplies the fresh `bao operator init` root token out-of-band
15+
# in the OPTIONAL Secret `openbao-bootstrap-token` (key root-token) — created at
16+
# DR time exactly like openbao-unseal-keys, NEVER committed to git.
17+
defaultNamespace: openbao
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: openbao-bootstrap-script
5+
namespace: openbao
6+
data:
7+
bootstrap.sh: |
8+
#!/bin/sh
9+
# Idempotent OpenBao internal-config bootstrap. Safe to re-run any time.
10+
# Recreates the structural config that lives in raft (and is otherwise only
11+
# imperative): KV v2 mount, kubernetes auth method+config, policies, roles.
12+
# Secret VALUES are NOT handled here — those come from the mirror (P2) or the
13+
# age cold-export (P3b). This only rebuilds the scaffolding.
14+
set -eu
15+
export BAO_ADDR="${BAO_ADDR:-http://openbao.openbao.svc:8200}"
16+
17+
# --- obtain a privileged token -------------------------------------------
18+
# DR mode: fresh `bao operator init` root token mounted out-of-band.
19+
# Steady mode: k8s-auth as the admin role (SA openbao-admin).
20+
if [ -f /bootstrap/root-token ]; then
21+
echo "[bootstrap] using root token from mounted Secret (DR mode)"
22+
BAO_TOKEN=$(cat /bootstrap/root-token)
23+
else
24+
echo "[bootstrap] no root-token Secret -> k8s-auth login role=admin"
25+
JWT=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
26+
RESP=$(bao write -format=json auth/kubernetes/login role=admin jwt="$JWT")
27+
BAO_TOKEN=$(printf '%s' "$RESP" | grep '"client_token"' | head -1 \
28+
| sed -E 's/.*"client_token": ?"([^"]+)".*/\1/')
29+
fi
30+
[ -n "${BAO_TOKEN:-}" ] || { echo "[bootstrap] FATAL: no token obtained"; exit 1; }
31+
export BAO_TOKEN
32+
33+
# --- 1. KV v2 secrets engine at mdapi/ -----------------------------------
34+
if bao secrets list -format=json 2>/dev/null | grep -q '"mdapi/"'; then
35+
echo "[bootstrap] kv mdapi/ already enabled"
36+
else
37+
echo "[bootstrap] enabling kv-v2 at mdapi/"
38+
bao secrets enable -path=mdapi -version=2 kv
39+
fi
40+
# Cap version churn from the hourly mirror (was max_versions=0 = unlimited).
41+
bao write mdapi/config max_versions=10
42+
43+
# --- 2. kubernetes auth method -------------------------------------------
44+
if bao auth list -format=json 2>/dev/null | grep -q '"kubernetes/"'; then
45+
echo "[bootstrap] kubernetes auth already enabled"
46+
else
47+
echo "[bootstrap] enabling kubernetes auth"
48+
bao auth enable kubernetes
49+
fi
50+
bao write auth/kubernetes/config \
51+
kubernetes_host="https://kubernetes.default.svc" \
52+
disable_iss_validation=true
53+
54+
# --- 3. policies (idempotent overwrite) ----------------------------------
55+
bao policy write admin - <<'POL'
56+
path "*" { capabilities = ["create","read","update","delete","list","sudo"] }
57+
POL
58+
bao policy write eso-read - <<'POL'
59+
path "mdapi/data/*" { capabilities = ["read"] }
60+
path "mdapi/metadata/*" { capabilities = ["read","list"] }
61+
POL
62+
bao policy write mirror - <<'POL'
63+
path "mdapi/data/*" { capabilities = ["create","update","read"] }
64+
path "mdapi/metadata/*" { capabilities = ["list","read"] }
65+
POL
66+
bao policy write archive-read - <<'POL'
67+
path "mdapi/data/*" { capabilities = ["read"] }
68+
path "mdapi/metadata/*" { capabilities = ["list","read"] }
69+
POL
70+
bao policy write snapshot-read - <<'POL'
71+
path "sys/storage/raft/snapshot" { capabilities = ["read"] }
72+
POL
73+
74+
# --- 4. kubernetes-auth roles (idempotent overwrite) ---------------------
75+
bao write auth/kubernetes/role/admin \
76+
bound_service_account_names=openbao-admin \
77+
bound_service_account_namespaces=openbao \
78+
policies=admin ttl=1h
79+
bao write auth/kubernetes/role/eso \
80+
bound_service_account_names=openbao-eso \
81+
bound_service_account_namespaces=external-secrets \
82+
policies=eso-read ttl=20m
83+
bao write auth/kubernetes/role/mirror \
84+
bound_service_account_names=openbao-mirror \
85+
bound_service_account_namespaces=openbao \
86+
policies=mirror ttl=30m
87+
bao write auth/kubernetes/role/windmill-archive \
88+
bound_service_account_names=windmill \
89+
bound_service_account_namespaces=windmill \
90+
policies=archive-read ttl=20m
91+
bao write auth/kubernetes/role/windmill-snapshot \
92+
bound_service_account_names=windmill \
93+
bound_service_account_namespaces=windmill \
94+
policies=snapshot-read ttl=10m
95+
96+
echo "[bootstrap] OpenBao internal config asserted OK"
97+
echo "[bootstrap] policies:"; bao policy list
98+
echo "[bootstrap] k8s-auth roles:"; bao list auth/kubernetes/role
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
apiVersion: batch/v1
2+
kind: CronJob
3+
metadata:
4+
name: openbao-bootstrap
5+
namespace: openbao
6+
spec:
7+
schedule: "0 0 1 1 *" # never matters — suspended; trigger on demand
8+
suspend: true # GitOps-mutable, never auto-fires
9+
concurrencyPolicy: Forbid
10+
successfulJobsHistoryLimit: 1
11+
failedJobsHistoryLimit: 3
12+
jobTemplate:
13+
spec:
14+
backoffLimit: 1
15+
activeDeadlineSeconds: 300
16+
template:
17+
spec:
18+
serviceAccountName: openbao-admin # bound to k8s-auth role `admin`
19+
restartPolicy: Never
20+
containers:
21+
- name: bootstrap
22+
image: openbao/openbao:2.5.5 # provides the `bao` CLI
23+
imagePullPolicy: IfNotPresent
24+
command: ["/bin/sh","/script/bootstrap.sh"]
25+
env:
26+
- name: BAO_ADDR
27+
value: http://openbao.openbao.svc:8200
28+
resources:
29+
requests: {cpu: 50m, memory: 64Mi}
30+
limits: {memory: 128Mi}
31+
volumeMounts:
32+
- {name: script, mountPath: /script}
33+
- {name: bootstrap-token, mountPath: /bootstrap, readOnly: true}
34+
volumes:
35+
- {name: script, configMap: {name: openbao-bootstrap-script}}
36+
# Optional: present ONLY during DR-from-scratch (key: root-token), like
37+
# openbao-unseal-keys. Absent in steady state -> k8s-auth path is used.
38+
- {name: bootstrap-token, secret: {secretName: openbao-bootstrap-token, optional: true}}

0 commit comments

Comments
 (0)