diff --git a/scripts/renew-runtime-lease.sh b/scripts/renew-runtime-lease.sh index 67fb69d..559e068 100755 --- a/scripts/renew-runtime-lease.sh +++ b/scripts/renew-runtime-lease.sh @@ -1,23 +1,127 @@ #!/usr/bin/env bash -# Attended remint of the ESO orphan token, then force-sync the runtime lease. -# Never prints secret values. Run inside: +# Contained remint of the ESO orphan token, then force-sync the runtime lease. +# +# Must be silent: `warden access --exec` fails closed on any child stdout/stderr +# even when the command succeeds. Persist metadata only. Never print a token. +# # warden access openbao-platform-admin-login --exec -- \ # env RAILIANCE01_KUBECONFIG="$HOME/.kube/config-railiance01" \ -# "$PWD/scripts/renew-runtime-lease.sh" +# BAO_ADDR="${BAO_ADDR:-https://bao.coulomb.social}" \ +# /home/worsch/audit-core/scripts/renew-runtime-lease.sh set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -export RAILIANCE01_KUBECONFIG="${RAILIANCE01_KUBECONFIG:-$HOME/.kube/config-railiance01}" +export RAILIANCE01_KUBECONFIG="${RAILIANCE01_KUBECONFIG:-/home/worsch/.kube/config-railiance01}" export KUBECONFIG="$RAILIANCE01_KUBECONFIG" export BAO_ADDR="${BAO_ADDR:-https://bao.coulomb.social}" +# Contained login persists the session in $HOME/.vault-token and unsets +# BAO_TOKEN/VAULT_TOKEN. Do not prompt; do not re-export a token. +unset BAO_TOKEN VAULT_TOKEN OPENBAO_TOKEN || true -"$ROOT/scripts/openbao-eso-token-apply.sh" +SECRET_NAME="${OPENBAO_AUDIT_CORE_ESO_SECRET:-openbao-audit-core-eso-token}" +SECRET_NS="${OPENBAO_AUDIT_CORE_ESO_NAMESPACE:-external-secrets}" +POLICY="${OPENBAO_AUDIT_CORE_POLICIES:-external-secrets-audit-core}" +TTL="${OPENBAO_AUDIT_CORE_ESO_TTL:-768h}" +EVIDENCE="$ROOT/docs/evidence/$(date -u +%Y-%m-%d)-eso-token-remint.json" +WORKDIR="${HOME:-/tmp}/audit-core-remint" +mkdir -p "$WORKDIR" +chmod 700 "$WORKDIR" 2>/dev/null || true -# One read of database/creds/audit-core-runtime = one new lease. Annotate -# only the runtime ExternalSecret; migrate/senders follow on their own -# refresh once the store is Ready. -kubectl -n audit-core annotate externalsecret audit-core-database \ - force-sync="$(date -u +%s)" --overwrite +_write_evidence() { + python3 - "$EVIDENCE" "$@" <<'PY' >/dev/null 2>&1 || true +import json, sys +from datetime import datetime, timezone +from pathlib import Path +path, step = Path(sys.argv[1]), sys.argv[2] +extra = sys.argv[3] if len(sys.argv) > 3 else "{}" +try: + payload = json.loads(extra) +except Exception: + payload = {} +doc = { + "step": step, + "observed_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(), + "secret": "external-secrets/openbao-audit-core-eso-token", + "lease_path": "database/creds/audit-core-runtime", +} +doc.update(payload) +path.parent.mkdir(parents=True, exist_ok=True) +path.write_text(json.dumps(doc, indent=2) + "\n") +PY +} -echo "ESO token reminted and audit-core-database force-sync requested." -echo "Wait for ExternalSecret Ready=True; do not restart the receiver." +_write_evidence started '{"kubeconfig_set": true}' + +if ! command -v bao >/dev/null 2>&1; then + _write_evidence failed '{"reason": "bao_missing"}' + exit 1 +fi +if ! command -v kubectl >/dev/null 2>&1; then + _write_evidence failed '{"reason": "kubectl_missing"}' + exit 1 +fi +if [[ ! -f "$RAILIANCE01_KUBECONFIG" ]]; then + _write_evidence failed '{"reason": "kubeconfig_missing"}' + exit 1 +fi +if ! kubectl get ns audit-core >/dev/null 2>&1; then + _write_evidence failed '{"reason": "not_railiance01"}' + exit 1 +fi +if kubectl get ns core-hub-staging >/dev/null 2>&1; then + _write_evidence failed '{"reason": "coulombcore_kubeconfig"}' + exit 1 +fi + +health="$(curl -fsS "$BAO_ADDR/v1/sys/health" 2>/dev/null || true)" +if printf '%s' "$health" | grep -q '"sealed":true'; then + _write_evidence failed '{"reason": "openbao_sealed"}' + exit 1 +fi + +token_json_file="$WORKDIR/token-create.json" +rm -f "$token_json_file" +if ! bao token create -policy="$POLICY" -ttl="$TTL" -renewable=true -orphan -format=json \ + >"$token_json_file" 2>/dev/null; then + _write_evidence failed '{"reason": "token_create_failed"}' + rm -f "$token_json_file" + exit 1 +fi + +child_file="$WORKDIR/eso.token" +python3 - "$token_json_file" "$child_file" <<'PY' >/dev/null 2>&1 +import json, sys +from pathlib import Path +raw = json.loads(Path(sys.argv[1]).read_text()) +token = (raw.get("auth") or {}).get("client_token") or "" +if len(token) < 8: + raise SystemExit(1) +Path(sys.argv[2]).write_text(token) +Path(sys.argv[2]).chmod(0o600) +PY +create_status=$? +rm -f "$token_json_file" +if [[ $create_status -ne 0 || ! -s "$child_file" ]]; then + _write_evidence failed '{"reason": "token_parse_failed"}' + rm -f "$child_file" + exit 1 +fi +_write_evidence token_created '{"ttl": "768h", "orphan": true, "renewable": true}' + +kubectl -n "$SECRET_NS" delete secret "$SECRET_NAME" --ignore-not-found >/dev/null 2>&1 || true +if ! kubectl -n "$SECRET_NS" create secret generic "$SECRET_NAME" \ + --from-file=token="$child_file" >/dev/null 2>&1; then + _write_evidence failed '{"reason": "secret_create_failed"}' + rm -f "$child_file" + exit 1 +fi +rm -f "$child_file" +_write_evidence secret_replaced '{"namespace": "external-secrets"}' + +if ! kubectl -n audit-core annotate externalsecret audit-core-database \ + force-sync="$(date -u +%s)" --overwrite >/dev/null 2>&1; then + _write_evidence failed '{"reason": "annotate_failed"}' + exit 1 +fi +_write_evidence annotated '{"externalsecret": "audit-core/audit-core-database"}' +exit 0