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:
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
.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 := $(KUBECTL_BIN) --kubeconfig=$(KUBECONFIG)
HELM := helm --kubeconfig=$(KUBECONFIG)

View File

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

View File

@@ -80,6 +80,11 @@ read_token() {
printf '%s\n' "$token"
}
kubectl_exec() {
# shellcheck disable=SC2086
$KUBECTL "$@"
}
remote_bao() {
local token="$1"
shift
@@ -87,7 +92,7 @@ remote_bao() {
printf 'DRY-RUN: bao %s\n' "$*"
return 0
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 "$@"
}
@@ -103,10 +108,36 @@ write_policy() {
printf 'DRY-RUN: bao policy write %s %s\n' "$name" "$file"
return 0
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"
}
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() {
local token="$1"
local output status
@@ -146,19 +177,21 @@ PY
return 0
fi
python3 - "$token" "$ROLES_SPEC" "$SSH_MOUNT" "$OPENBAO_NAMESPACE" "$pod" "$KUBECTL" <<'PY'
import shlex
import subprocess
import sys
token, spec_path, mount, namespace, pod, kubectl = sys.argv[1:7]
import yaml
kubectl_parts = shlex.split(kubectl) if kubectl else ["kubectl"]
spec = yaml.safe_load(open(spec_path))
roles = spec.get("roles") or {}
for role_name, params in roles.items():
args = [f"{k}={v}" for k, v in params.items()]
cmd = ["bao", "write", f"{mount}/roles/{role_name}"] + args
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,
input=(token + "\n").encode(),
capture_output=True,
@@ -177,9 +210,12 @@ export_ca_pubkey() {
return 0
fi
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
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
fi
local fingerprint
@@ -197,10 +233,10 @@ export_ca_pubkey() {
local tmp
tmp="$(mktemp)"
printf '%s\n' "$pubkey" >"$tmp"
$KUBECTL create secret generic "$K8S_CA_SECRET" \
kubectl_exec create secret generic "$K8S_CA_SECRET" \
--namespace "$OPENBAO_NAMESPACE" \
--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"
printf 'OK: K8s secret %s/%s updated\n' "$OPENBAO_NAMESPACE" "$K8S_CA_SECRET"
fi
@@ -214,6 +250,7 @@ fi
remote_bao "$token" status
enable_ssh_engine "$token"
ensure_default_issuer "$token"
apply_roles "$token"
write_policy "$token" warden-sign "$POLICY_DIR/warden-sign.hcl"
remote_bao "$token" list "${SSH_MOUNT}/roles"