diff --git a/Makefile b/Makefile index 625dce3..59cf93a 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/openbao/ssh/roles-spec.yaml b/openbao/ssh/roles-spec.yaml index b8fdf78..1ccb34f 100644 --- a/openbao/ssh/roles-spec.yaml +++ b/openbao/ssh/roles-spec.yaml @@ -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 \ No newline at end of file diff --git a/scripts/openbao-apply-ssh-engine.sh b/scripts/openbao-apply-ssh-engine.sh index 92f1b11..1b78cc4 100755 --- a/scripts/openbao-apply-ssh-engine.sh +++ b/scripts/openbao-apply-ssh-engine.sh @@ -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"