Skip to content

Commit 9303263

Browse files
committed
feat(server): add self-init bootstrap job mode
Signed-off-by: Roel de Cort <roel.decort@secretz.io>
1 parent 85701a0 commit 9303263

15 files changed

Lines changed: 799 additions & 10 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Unreleased
22

3+
## 0.29.0
4+
5+
- feat(server): add self-init bootstrap job mode
6+
37
## 0.28.4
48

59
- docs: Update commented example with missing field

charts/openbao/Chart.yaml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
apiVersion: v2
55
name: openbao
6-
version: 0.28.4
6+
version: 0.29.0
77
appVersion: v2.5.5
88
kubeVersion: ">= 1.30.0-0"
99
description: Official OpenBao Chart
@@ -25,14 +25,11 @@ sources:
2525
annotations:
2626
charts.openshift.io/name: OpenBao
2727
charts.openshift.io/provider: OpenBao
28-
artifacthub.io/containsSecurityUpdates: "true"
28+
artifacthub.io/containsSecurityUpdates: "false"
2929
artifacthub.io/changes: |
30-
- kind: changed
30+
- kind: added
3131
description: |
32-
docs: Update commented example with missing field
33-
- kind: changed
34-
description: |
35-
chore: bump OpenBao to v2.5.5
32+
feat(server): add self-init bootstrap job mode
3633
maintainers:
3734
- name: OpenBao
3835
email: openbao-security@lists.openssf.org

charts/openbao/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# openbao
22

3-
![Version: 0.28.4](https://img.shields.io/badge/Version-0.28.4-informational?style=flat-square) ![AppVersion: v2.5.5](https://img.shields.io/badge/AppVersion-v2.5.5-informational?style=flat-square)
3+
![Version: 0.29.0](https://img.shields.io/badge/Version-0.29.0-informational?style=flat-square) ![AppVersion: v2.5.5](https://img.shields.io/badge/AppVersion-v2.5.5-informational?style=flat-square)
44

55
Official OpenBao Chart
66

@@ -215,7 +215,7 @@ Kubernetes: `>= 1.30.0-0`
215215
| server.ha.disruptionBudget.enabled | bool | `true` | |
216216
| server.ha.disruptionBudget.maxUnavailable | string | `nil` | |
217217
| server.ha.enabled | bool | `false` | |
218-
| server.ha.raft.config | string | `"ui = true\n\nlistener \"tcp\" {\n tls_disable = 1\n address = \"[::]:8200\"\n cluster_address = \"[::]:8201\"\n # Enable unauthenticated metrics access (necessary for Prometheus Operator)\n #telemetry {\n # unauthenticated_metrics_access = \"true\"\n #}\n}\n\nstorage \"raft\" {\n path = \"/openbao/data\"\n}\n\nservice_registration \"kubernetes\" {}\n"` | |
218+
| server.ha.raft.config | string | `"ui = true\n\nlistener \"tcp\" {\n tls_disable = 1\n address = \"[::]:8200\"\n cluster_address = \"[::]:8201\"\n # Enable unauthenticated metrics access (necessary for Prometheus Operator)\n #telemetry {\n # unauthenticated_metrics_access = \"true\"\n #}\n}\n\nstorage \"raft\" {\n path = \"/openbao/data\"\n {{- include \"openbao.selfInit.raftRetryJoin\" . | nindent 10 }}\n}\n\nservice_registration \"kubernetes\" {}\n"` | |
219219
| server.ha.raft.enabled | bool | `false` | |
220220
| server.ha.raft.setNodeId | bool | `false` | |
221221
| server.ha.replicas | int | `3` | |
@@ -273,6 +273,18 @@ Kubernetes: `>= 1.30.0-0`
273273
| server.route.host | string | `"chart-example.local"` | |
274274
| server.route.labels | object | `{}` | |
275275
| server.route.tls.termination | string | `"passthrough"` | |
276+
| server.selfInit.config | string | `""` | Configure OpenBao declarative self-initialization HCL. |
277+
| server.selfInit.enabled | bool | `false` | Enable OpenBao declarative self-initialization configuration. |
278+
| server.selfInit.job.activeDeadlineSeconds | string | `nil` | Optional Job activeDeadlineSeconds. |
279+
| server.selfInit.job.annotations | object | `{}` | Annotations to apply to the self-init bootstrap Job. |
280+
| server.selfInit.job.autopilot.deadServerLastContactThreshold | string | `"1m"` | Dead-server last-contact threshold for the generated autopilot cleanup request. |
281+
| server.selfInit.job.autopilot.enabled | bool | `true` | Generate an autopilot cleanup request with the self-init configuration. |
282+
| server.selfInit.job.autopilot.minQuorum | string | `nil` | Minimum Raft quorum for autopilot dead-server cleanup. Defaults to max(3, server.ha.replicas) when unset. |
283+
| server.selfInit.job.autopilot.serverStabilizationTime | string | `"10s"` | Server stabilization time for the generated autopilot cleanup request. |
284+
| server.selfInit.job.backoffLimit | int | `6` | Job backoff limit. |
285+
| server.selfInit.job.holdSeconds | int | `120` | Number of seconds the bootstrap Job keeps OpenBao running after it observes initialization, giving StatefulSet pods time to retry_join. |
286+
| server.selfInit.job.podAnnotations | object | `{}` | Annotations to apply to the self-init bootstrap Job pod. |
287+
| server.selfInit.job.ttlSecondsAfterFinished | string | `nil` | Optional Job ttlSecondsAfterFinished. Leave unset for GitOps so the completed bootstrap Job remains part of observed cluster state. |
276288
| server.service.active.annotations | object | `{}` | |
277289
| server.service.active.enabled | bool | `true` | |
278290
| server.service.active.extraLabels | object | `{}` | |

charts/openbao/templates/_helpers.tpl

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,19 @@ Set's the replica count based on the different modes configured by user
205205
{{ end }}
206206
{{- end -}}
207207

208+
{{/*
209+
Set's the StatefulSet pod management policy. Self-init uses a temporary
210+
bootstrap Job, so server pods must start in parallel to avoid blocking on
211+
ordinal 0 while joining the bootstrap server.
212+
*/}}
213+
{{- define "openbao.serverPodManagementPolicy" -}}
214+
{{- if include "openbao.selfInit.job.enabled" . -}}
215+
Parallel
216+
{{- else -}}
217+
{{ .Values.server.podManagementPolicy }}
218+
{{- end -}}
219+
{{- end -}}
220+
208221
{{/*
209222
Set's up configmap mounts if this isn't a dev deployment and the user
210223
defined a custom configuration. Additionally iterates over any
@@ -1139,10 +1152,202 @@ Supported inputs are Values.ui
11391152
{{- end }}
11401153
{{- end -}}
11411154

1155+
{{/*
1156+
Returns true when declarative self-initialization config should be rendered.
1157+
*/}}
1158+
{{- define "openbao.selfInit.enabled" -}}
1159+
{{- if and .Values.server.selfInit.enabled .Values.server.selfInit.config -}}true{{- end -}}
1160+
{{- end -}}
1161+
1162+
{{/*
1163+
Returns true when self-init config is delivered through the bootstrap Job.
1164+
*/}}
1165+
{{- define "openbao.selfInit.job.enabled" -}}
1166+
{{- if and (include "openbao.selfInit.enabled" .) (eq .mode "ha") (eq (.Values.server.ha.raft.enabled | toString) "true") -}}true{{- end -}}
1167+
{{- end -}}
1168+
1169+
{{/*
1170+
Name of the temporary self-init bootstrap Service.
1171+
*/}}
1172+
{{- define "openbao.selfInit.job.serviceName" -}}
1173+
{{ template "openbao.fullname" . }}-self-init
1174+
{{- end -}}
1175+
1176+
{{/*
1177+
Generation hash for the self-init bootstrap Job. The hash is based on inputs
1178+
that affect the Job pod template so immutable Job template changes produce a
1179+
new Job instead of patching the old one.
1180+
*/}}
1181+
{{- define "openbao.selfInit.job.generation" -}}
1182+
{{- $source := dict
1183+
"args" (include "openbao.selfInit.job.args" .)
1184+
"config" (include "openbao.selfInit.job.config" .)
1185+
"containerSecurityContext" (include "server.statefulSet.securityContext.container" .)
1186+
"dataStorageMountPath" .Values.server.dataStorage.mountPath
1187+
"extraArgs" .Values.server.extraArgs
1188+
"extraEnvironmentVars" .Values.server.extraEnvironmentVars
1189+
"extraSecretEnvironmentVars" .Values.server.extraSecretEnvironmentVars
1190+
"extraVolumes" .Values.server.extraVolumes
1191+
"hostAliases" .Values.server.hostAliases
1192+
"image" .Values.server.image
1193+
"imagePullSecrets" (include "imagePullSecrets" .)
1194+
"logFormat" .Values.server.logFormat
1195+
"logLevel" .Values.server.logLevel
1196+
"openbaoScheme" (include "openbao.scheme" .)
1197+
"placement" (dict "affinity" .Values.server.affinity "nodeSelector" .Values.server.nodeSelector "priorityClassName" .Values.server.priorityClassName "tolerations" .Values.server.tolerations "topologySpreadConstraints" .Values.server.topologySpreadConstraints)
1198+
"podAnnotations" .Values.server.selfInit.job.podAnnotations
1199+
"podSecurityContext" (include "server.statefulSet.securityContext.pod" .)
1200+
"raftSetNodeId" .Values.server.ha.raft.setNodeId
1201+
"resources" .Values.server.resources
1202+
"selfInitConfig" (include "openbao.selfInit.config" .)
1203+
"serviceAccountName" (include "openbao.serviceAccount.name" .)
1204+
"servicePort" .Values.server.service.port
1205+
"serviceTargetPort" .Values.server.service.targetPort
1206+
"volumeMounts" .Values.server.volumeMounts
1207+
"volumes" .Values.server.volumes
1208+
-}}
1209+
{{- toJson $source | sha256sum | trunc 10 -}}
1210+
{{- end -}}
1211+
1212+
{{/*
1213+
Name of the self-init bootstrap Job. The suffix avoids immutable Job template
1214+
patches during Helm or GitOps reconciliation.
1215+
*/}}
1216+
{{- define "openbao.selfInit.job.name" -}}
1217+
{{- $prefix := printf "%s-self-init" (include "openbao.fullname" .) | trunc 52 | trimSuffix "-" -}}
1218+
{{- printf "%s-%s" $prefix (include "openbao.selfInit.job.generation" .) -}}
1219+
{{- end -}}
1220+
1221+
{{/*
1222+
Generated Raft retry_join stanzas for self-init bootstrap Job mode.
1223+
1224+
This helper must be rendered inside the storage "raft" block.
1225+
*/}}
1226+
{{- define "openbao.selfInit.raftRetryJoin" -}}
1227+
{{- $root := . -}}
1228+
{{- if include "openbao.selfInit.job.enabled" $root }}
1229+
retry_join {
1230+
leader_api_addr = "{{ include "openbao.scheme" $root }}://{{ include "openbao.selfInit.job.serviceName" $root }}.{{ include "openbao.namespace" $root }}.svc:{{ $root.Values.server.service.port }}"
1231+
}
1232+
{{- $replicas := int (include "openbao.replicas" $root) }}
1233+
{{- range $i := until $replicas }}
1234+
retry_join {
1235+
leader_api_addr = "{{ include "openbao.scheme" $root }}://{{ include "openbao.fullname" $root }}-{{ $i }}.{{ include "openbao.fullname" $root }}-internal.{{ include "openbao.namespace" $root }}.svc:{{ $root.Values.server.service.port }}"
1236+
}
1237+
{{- end }}
1238+
{{- end }}
1239+
{{- end -}}
1240+
1241+
{{/*
1242+
Render the bootstrap Job's base server config without generated retry_join
1243+
stanzas. The Job initializes an empty temporary Raft data directory; StatefulSet
1244+
pods join the Job, not the other way around.
1245+
*/}}
1246+
{{- define "openbao.selfInit.job.config" -}}
1247+
{{- $jobCtx := deepCopy . -}}
1248+
{{- $_ := set $jobCtx.Values.server.selfInit "enabled" false -}}
1249+
{{ tpl .Values.server.ha.raft.config $jobCtx | nindent 4 | trim }}
1250+
{{- end -}}
1251+
1252+
{{/*
1253+
Set's the args for the self-init bootstrap Job.
1254+
*/}}
1255+
{{- define "openbao.selfInit.job.args" -}}
1256+
- |
1257+
{{- $replicas := int (include "openbao.replicas" .) }}
1258+
{{- range $i := until $replicas }}
1259+
if BAO_ADDR="{{ include "openbao.scheme" $ }}://{{ include "openbao.fullname" $ }}-{{ $i }}.{{ include "openbao.fullname" $ }}-internal.{{ include "openbao.namespace" $ }}.svc:{{ $.Values.server.service.port }}" bao status -tls-skip-verify -format=json 2>/dev/null | grep -Eq '"initialized"[[:space:]]*:[[:space:]]*true'; then
1260+
echo "OpenBao is already initialized; skipping self-init bootstrap.";
1261+
exit 0;
1262+
fi;
1263+
{{- end }}
1264+
cp /openbao/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl;
1265+
[ -s /openbao/self-init/self-init.hcl ] && cat /openbao/self-init/self-init.hcl >> /tmp/storageconfig.hcl;
1266+
[ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /tmp/storageconfig.hcl;
1267+
[ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /tmp/storageconfig.hcl;
1268+
[ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /tmp/storageconfig.hcl;
1269+
[ -n "${API_ADDR}" ] && sed -Ei "s|API_ADDR|${API_ADDR?}|g" /tmp/storageconfig.hcl;
1270+
[ -n "${TRANSIT_ADDR}" ] && sed -Ei "s|TRANSIT_ADDR|${TRANSIT_ADDR?}|g" /tmp/storageconfig.hcl;
1271+
[ -n "${RAFT_ADDR}" ] && sed -Ei "s|RAFT_ADDR|${RAFT_ADDR?}|g" /tmp/storageconfig.hcl;
1272+
/usr/local/bin/docker-entrypoint.sh bao server -config=/tmp/storageconfig.hcl {{ .Values.server.extraArgs }} &
1273+
pid="$!";
1274+
trap 'kill -TERM "${pid}" 2>/dev/null || true; wait "${pid}" 2>/dev/null || true' TERM INT;
1275+
until BAO_ADDR="{{ include "openbao.scheme" . }}://127.0.0.1:8200" bao status -tls-skip-verify -format=json 2>/dev/null | grep -Eq '"initialized"[[:space:]]*:[[:space:]]*true'; do
1276+
if ! kill -0 "${pid}" 2>/dev/null; then
1277+
wait "${pid}";
1278+
exit $?;
1279+
fi;
1280+
sleep 2;
1281+
done;
1282+
sleep {{ .Values.server.selfInit.job.holdSeconds }};
1283+
kill -TERM "${pid}" 2>/dev/null || true;
1284+
wait "${pid}" 2>/dev/null || true;
1285+
{{- end -}}
1286+
1287+
{{/*
1288+
Validate declarative self-initialization configuration.
1289+
*/}}
1290+
{{- define "openbao.selfInit.validate" -}}
1291+
{{- if and .Values.server.selfInit.enabled .Values.server.selfInit.config (ne .mode "dev") (ne .mode "external") }}
1292+
{{- if not (and (eq .mode "ha") (eq (.Values.server.ha.raft.enabled | toString) "true")) }}
1293+
{{- fail "server.selfInit.enabled requires server.ha.enabled=true and server.ha.raft.enabled=true" }}
1294+
{{- end }}
1295+
{{- if .Values.server.selfInit.job.autopilot.enabled }}
1296+
{{- $minQuorum := int (include "openbao.selfInit.autopilotMinQuorum" .) -}}
1297+
{{- if lt $minQuorum 3 }}
1298+
{{- fail "server.selfInit.job.autopilot.minQuorum must be at least 3" }}
1299+
{{- end }}
1300+
{{- $replicas := int (include "openbao.replicas" .) -}}
1301+
{{- if lt $replicas $minQuorum }}
1302+
{{- fail "server.selfInit.job.autopilot.minQuorum must be less than or equal to server.ha.replicas" }}
1303+
{{- end }}
1304+
{{- end }}
1305+
{{- if ne (typeOf .Values.server.ha.raft.config) "string" }}
1306+
{{- fail "server.selfInit.config requires server.ha.raft.config to be a string" }}
1307+
{{- end }}
1308+
{{- end }}
1309+
{{- end -}}
1310+
1311+
{{- define "openbao.selfInit.autopilotMinQuorum" -}}
1312+
{{- $configured := .Values.server.selfInit.job.autopilot.minQuorum -}}
1313+
{{- if kindIs "invalid" $configured -}}
1314+
{{- $replicas := int (include "openbao.replicas" .) -}}
1315+
{{- if lt $replicas 3 -}}3{{- else -}}{{ $replicas }}{{- end -}}
1316+
{{- else -}}
1317+
{{- $configured -}}
1318+
{{- end -}}
1319+
{{- end -}}
1320+
1321+
{{- define "openbao.selfInit.autopilotConfig" -}}
1322+
initialize "openbao_self_init_autopilot" {
1323+
request "configure-autopilot" {
1324+
operation = "update"
1325+
path = "sys/storage/raft/autopilot/configuration"
1326+
data = {
1327+
cleanup_dead_servers = true
1328+
dead_server_last_contact_threshold = "{{ .Values.server.selfInit.job.autopilot.deadServerLastContactThreshold }}"
1329+
server_stabilization_time = "{{ .Values.server.selfInit.job.autopilot.serverStabilizationTime }}"
1330+
min_quorum = {{ include "openbao.selfInit.autopilotMinQuorum" . }}
1331+
}
1332+
}
1333+
}
1334+
{{- end -}}
1335+
1336+
{{- define "openbao.selfInit.config" -}}
1337+
{{- if and .Values.server.selfInit.enabled .Values.server.selfInit.config }}
1338+
{{- if .Values.server.selfInit.job.autopilot.enabled }}
1339+
{{ include "openbao.selfInit.autopilotConfig" . | trim }}
1340+
1341+
{{- end }}
1342+
{{ tpl .Values.server.selfInit.config . | trim }}
1343+
{{- end }}
1344+
{{- end -}}
1345+
11421346
{{/*
11431347
config file from values
11441348
*/}}
11451349
{{- define "openbao.config" -}}
1350+
{{- include "openbao.selfInit.validate" . }}
11461351
{{- if or (eq .mode "ha") (eq .mode "standalone") }}
11471352
{{- $type := typeOf (index .Values.server .mode).config }}
11481353
{{- if eq $type "string" }}

charts/openbao/templates/server-config-configmap.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ SPDX-License-Identifier: MPL-2.0
44
*/}}
55

66
{{ template "openbao.mode" . }}
7+
{{- include "openbao.selfInit.validate" . }}
78
{{- if ne .mode "external" }}
89
{{- if .serverEnabled -}}
910
{{- if ne .mode "dev" -}}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{{ template "openbao.mode" . }}
2+
{{- include "openbao.selfInit.validate" . }}
3+
{{- if include "openbao.selfInit.job.enabled" . -}}
4+
apiVersion: v1
5+
kind: ConfigMap
6+
metadata:
7+
name: {{ template "openbao.fullname" . }}-self-init-config
8+
namespace: {{ include "openbao.namespace" . }}
9+
labels:
10+
helm.sh/chart: {{ include "openbao.chart" . }}
11+
app.kubernetes.io/name: {{ include "openbao.name" . }}
12+
app.kubernetes.io/instance: {{ .Release.Name }}
13+
app.kubernetes.io/managed-by: {{ .Release.Service }}
14+
data:
15+
self-init.hcl: |-
16+
{{ include "openbao.selfInit.config" . | nindent 4 | trim }}
17+
{{- end }}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{{ template "openbao.mode" . }}
2+
{{- include "openbao.selfInit.validate" . }}
3+
{{- if include "openbao.selfInit.job.enabled" . -}}
4+
apiVersion: v1
5+
kind: ConfigMap
6+
metadata:
7+
name: {{ template "openbao.fullname" . }}-self-init-job-config
8+
namespace: {{ include "openbao.namespace" . }}
9+
labels:
10+
helm.sh/chart: {{ include "openbao.chart" . }}
11+
app.kubernetes.io/name: {{ include "openbao.name" . }}
12+
app.kubernetes.io/instance: {{ .Release.Name }}
13+
app.kubernetes.io/managed-by: {{ .Release.Service }}
14+
data:
15+
extraconfig-from-values.hcl: |-
16+
{{ include "openbao.selfInit.job.config" . }}
17+
{{- end }}

0 commit comments

Comments
 (0)