the-custodian/docs/changes/CUST-WP-0073/workstation-agents-r4/issue-agent-credentials.sh
codex 158930139e
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Successful in 10s
CUST-WP-0073: step-9 Kubernetes denial proof from the agent account (597/597); verifier retries and separates grants from errors; issuance script
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:05:33 +02:00

53 lines
2.7 KiB
Bash
Executable file

#!/bin/bash
# CUST-WP-0073 step 7 — supervisor-only daily credential issuance for custodian-agents.
# Run from the OPERATOR account (never inside the custodian-agents distro).
# Issues a 24h ServiceAccount token and a 12h agt-custodian SSH certificate and
# writes them into the distro without printing either.
# issue-agent-credentials.sh [kube|ssh|all]
set -euo pipefail
what="${1:-all}"
distro=custodian-agents
api=https://127.0.0.1:16444 # bridge tunnel k3s-api-railiance01 (shared WSL2 loopback)
# --exec passes argv verbatim; plain `wsl.exe --` re-joins it through a shell.
in_distro() { wsl.exe -d "$distro" --cd /home/custodian-agt --exec "$@"; }
if [ "$what" = kube ] || [ "$what" = all ]; then
bridge status 2>/dev/null | grep -q 'k3s-api-railiance01 *connected' \
|| { echo 'bridge tunnel k3s-api-railiance01 is not connected (bridge up k3s-api-railiance01)' >&2; exit 1; }
ca_b64="$(ssh railiance01 'sudo k3s kubectl -n default get configmap kube-root-ca.crt -o jsonpath="{.data.ca\.crt}"' | base64 -w0)"
test -n "$ca_b64"
ssh railiance01 'sudo k3s kubectl -n custodian-agents create token custodian-agent --duration=24h' \
| in_distro python3 -c '
import os, sys
token = sys.stdin.read().strip()
assert token.count(".") == 2, "unexpected token shape"
ca, api = sys.argv[1], sys.argv[2]
cfg = f"""apiVersion: v1
kind: Config
clusters:
- name: railiance01
cluster: {{server: {api}, certificate-authority-data: {ca}}}
users:
- name: custodian-agent
user: {{token: {token}}}
contexts:
- name: railiance01
context: {{cluster: railiance01, user: custodian-agent}}
current-context: railiance01
"""
path = os.path.expanduser("~/.kube/config")
fd = os.open(path + ".new", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
os.write(fd, cfg.encode()); os.close(fd); os.replace(path + ".new", path)
print("kubeconfig written (24h token, context railiance01)")
' "$ca_b64" "$api"
fi
if [ "$what" = ssh ] || [ "$what" = all ]; then
pub="$(mktemp)"; cert="$(mktemp)"; trap 'rm -f "$pub" "$cert"' EXIT
in_distro cat .ssh/id_ed25519.pub > "$pub"
# Sign first; only a non-empty certificate replaces the agent's current one.
warden sign agt-custodian --pubkey "$pub" > "$cert"
grep -q -- '-cert-v01@openssh.com ' "$cert" || { echo 'warden sign returned no certificate' >&2; exit 1; }
in_distro sh -c 'umask 022; cat > .ssh/id_ed25519-cert.pub && ssh-keygen -L -f .ssh/id_ed25519-cert.pub | grep -E "Principals|Valid|permit" -A1 | tr -s " "' < "$cert"
in_distro sh -c 'grep -q "^Host railiance01" .ssh/config 2>/dev/null || printf "Host railiance01\n HostName 92.205.62.239\n User custodian-agt\n IdentityFile ~/.ssh/id_ed25519\n CertificateFile ~/.ssh/id_ed25519-cert.pub\n IdentitiesOnly yes\n" >> .ssh/config; chmod 600 .ssh/config'
fi