fix(openbao): complete SSH apply script for OpenBao 2.5.x issuers

Generate default CA via ssh/config/ca, split composite KUBECTL for role writes,
read pubkey from config/ca, allow warden key_id in roles, prefer production kubeconfig.
This commit is contained in:
tegwick 2026-06-18 01:18:56 +02:00
parent c24956fb5a
commit 7838df6069
3 changed files with 48 additions and 8 deletions

View file

@ -1,7 +1,7 @@
SHELL := /usr/bin/env bash SHELL := /usr/bin/env bash
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
KUBECONFIG ?= $(firstword $(wildcard $(HOME)/.kube/config-hosteurope) $(HOME)/.kube/config) KUBECONFIG ?= $(firstword $(wildcard $(HOME)/.kube/config) $(HOME)/.kube/config-hosteurope)
KUBECTL_BIN ?= $(firstword $(shell command -v kubectl 2>/dev/null) $(wildcard $(HOME)/.local/bin/kubectl) kubectl) KUBECTL_BIN ?= $(firstword $(shell command -v kubectl 2>/dev/null) $(wildcard $(HOME)/.local/bin/kubectl) kubectl)
KUBECTL := $(KUBECTL_BIN) --kubeconfig=$(KUBECONFIG) KUBECTL := $(KUBECTL_BIN) --kubeconfig=$(KUBECONFIG)
HELM := helm --kubeconfig=$(KUBECONFIG) HELM := helm --kubeconfig=$(KUBECONFIG)

View file

@ -8,6 +8,7 @@ roles:
key_type: ca key_type: ca
allowed_users: "*" allowed_users: "*"
allow_user_certificates: true allow_user_certificates: true
allow_user_key_ids: true
default_user: adm default_user: adm
ttl: 48h ttl: 48h
max_ttl: 48h max_ttl: 48h
@ -15,6 +16,7 @@ roles:
key_type: ca key_type: ca
allowed_users: "*" allowed_users: "*"
allow_user_certificates: true allow_user_certificates: true
allow_user_key_ids: true
default_user: agt default_user: agt
ttl: 24h ttl: 24h
max_ttl: 24h max_ttl: 24h
@ -22,6 +24,7 @@ roles:
key_type: ca key_type: ca
allowed_users: "*" allowed_users: "*"
allow_user_certificates: true allow_user_certificates: true
allow_user_key_ids: true
default_user: atm default_user: atm
ttl: 8h ttl: 8h
max_ttl: 8h max_ttl: 8h

View file

@ -80,6 +80,11 @@ read_token() {
printf '%s\n' "$token" printf '%s\n' "$token"
} }
kubectl_exec() {
# shellcheck disable=SC2086
$KUBECTL "$@"
}
remote_bao() { remote_bao() {
local token="$1" local token="$1"
shift shift
@ -87,7 +92,7 @@ remote_bao() {
printf 'DRY-RUN: bao %s\n' "$*" printf 'DRY-RUN: bao %s\n' "$*"
return 0 return 0
fi fi
printf '%s\n' "$token" | $KUBECTL exec -i -n "$OPENBAO_NAMESPACE" "$pod" -- \ printf '%s\n' "$token" | kubectl_exec exec -i -n "$OPENBAO_NAMESPACE" "$pod" -- \
sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; exec bao "$@"' sh "$@" sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; exec bao "$@"' sh "$@"
} }
@ -103,10 +108,36 @@ write_policy() {
printf 'DRY-RUN: bao policy write %s %s\n' "$name" "$file" printf 'DRY-RUN: bao policy write %s %s\n' "$name" "$file"
return 0 return 0
fi fi
{ printf '%s\n' "$token"; cat "$file"; } | $KUBECTL exec -i -n "$OPENBAO_NAMESPACE" "$pod" -- \ { printf '%s\n' "$token"; cat "$file"; } | kubectl_exec exec -i -n "$OPENBAO_NAMESPACE" "$pod" -- \
sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; bao policy write "$1" -' sh "$name" sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; bao policy write "$1" -' sh "$name"
} }
ensure_default_issuer() {
local token="$1"
local issuers_out
if [ "$DRY_RUN" -eq 1 ]; then
printf 'DRY-RUN: bao read %s/config/issuers\n' "$SSH_MOUNT"
printf 'DRY-RUN: bao write %s/config/ca generate_signing_key=true key_type=ed25519\n' "$SSH_MOUNT"
return 0
fi
if issuers_out="$(remote_bao "$token" read "${SSH_MOUNT}/config/issuers" 2>&1)"; then
printf 'OK: default SSH issuer already configured.\n'
return 0
fi
case "$issuers_out" in
*"no default issuer"*)
remote_bao "$token" write "${SSH_MOUNT}/config/ca" \
generate_signing_key=true key_type=ed25519
printf 'OK: generated default SSH CA issuer.\n'
;;
*)
printf '%s\n' "$issuers_out" >&2
echo "ERROR: failed to read SSH issuer configuration" >&2
exit 1
;;
esac
}
enable_ssh_engine() { enable_ssh_engine() {
local token="$1" local token="$1"
local output status local output status
@ -146,19 +177,21 @@ PY
return 0 return 0
fi fi
python3 - "$token" "$ROLES_SPEC" "$SSH_MOUNT" "$OPENBAO_NAMESPACE" "$pod" "$KUBECTL" <<'PY' python3 - "$token" "$ROLES_SPEC" "$SSH_MOUNT" "$OPENBAO_NAMESPACE" "$pod" "$KUBECTL" <<'PY'
import shlex
import subprocess import subprocess
import sys import sys
token, spec_path, mount, namespace, pod, kubectl = sys.argv[1:7] token, spec_path, mount, namespace, pod, kubectl = sys.argv[1:7]
import yaml import yaml
kubectl_parts = shlex.split(kubectl) if kubectl else ["kubectl"]
spec = yaml.safe_load(open(spec_path)) spec = yaml.safe_load(open(spec_path))
roles = spec.get("roles") or {} roles = spec.get("roles") or {}
for role_name, params in roles.items(): for role_name, params in roles.items():
args = [f"{k}={v}" for k, v in params.items()] args = [f"{k}={v}" for k, v in params.items()]
cmd = ["bao", "write", f"{mount}/roles/{role_name}"] + args cmd = ["bao", "write", f"{mount}/roles/{role_name}"] + args
proc = subprocess.run( proc = subprocess.run(
[kubectl, "exec", "-i", "-n", namespace, pod, "--", "sh", "-c", kubectl_parts + ["exec", "-i", "-n", namespace, pod, "--", "sh", "-c",
"read -r BAO_TOKEN; export BAO_TOKEN; exec bao \"$@\"", "sh"] + cmd, "read -r BAO_TOKEN; export BAO_TOKEN; exec bao \"$@\"", "sh"] + cmd,
input=(token + "\n").encode(), input=(token + "\n").encode(),
capture_output=True, capture_output=True,
@ -177,9 +210,12 @@ export_ca_pubkey() {
return 0 return 0
fi fi
local pubkey local pubkey
pubkey="$(remote_bao "$token" read -field=public_key "${SSH_MOUNT}/public_key")" pubkey="$(remote_bao "$token" read -field=public_key "${SSH_MOUNT}/config/ca" 2>/dev/null || true)"
if [ -z "$pubkey" ]; then if [ -z "$pubkey" ]; then
warn "Could not read SSH CA public key from ${SSH_MOUNT}/public_key" pubkey="$(remote_bao "$token" read -field=public_key "${SSH_MOUNT}/public_key" 2>/dev/null || true)"
fi
if [ -z "$pubkey" ]; then
warn "Could not read SSH CA public key from ${SSH_MOUNT}/config/ca or ${SSH_MOUNT}/public_key"
return 0 return 0
fi fi
local fingerprint local fingerprint
@ -197,10 +233,10 @@ export_ca_pubkey() {
local tmp local tmp
tmp="$(mktemp)" tmp="$(mktemp)"
printf '%s\n' "$pubkey" >"$tmp" printf '%s\n' "$pubkey" >"$tmp"
$KUBECTL create secret generic "$K8S_CA_SECRET" \ kubectl_exec create secret generic "$K8S_CA_SECRET" \
--namespace "$OPENBAO_NAMESPACE" \ --namespace "$OPENBAO_NAMESPACE" \
--from-file=ca_user.pub="$tmp" \ --from-file=ca_user.pub="$tmp" \
--dry-run=client -o yaml | $KUBECTL apply -f - --dry-run=client -o yaml | kubectl_exec apply -f -
rm -f "$tmp" rm -f "$tmp"
printf 'OK: K8s secret %s/%s updated\n' "$OPENBAO_NAMESPACE" "$K8S_CA_SECRET" printf 'OK: K8s secret %s/%s updated\n' "$OPENBAO_NAMESPACE" "$K8S_CA_SECRET"
fi fi
@ -214,6 +250,7 @@ fi
remote_bao "$token" status remote_bao "$token" status
enable_ssh_engine "$token" enable_ssh_engine "$token"
ensure_default_issuer "$token"
apply_roles "$token" apply_roles "$token"
write_policy "$token" warden-sign "$POLICY_DIR/warden-sign.hcl" write_policy "$token" warden-sign "$POLICY_DIR/warden-sign.hcl"
remote_bao "$token" list "${SSH_MOUNT}/roles" remote_bao "$token" list "${SSH_MOUNT}/roles"