generated from coulomb/repo-seed
feat(creds): NK-WP-0005 — agent-driven credential bootstrap
Implements all 7 tasks from NK-WP-0005:
T01: creds-state.yaml → schema_version: 2, agent_mode: true
Replaces keepass_confirmed with emergency_bundle_delivered,
adds phase tracking fields for fully automated flow.
T02: creds-bootstrap-agent.sh — single entrypoint for autonomous
bootstrap. 10 phases, idempotent re-runs via state file.
Only human touchpoint: emergency bundle confirmation gate.
T03: emergency-bundle.sh — assembles and displays emergency bundle
(age key + break-glass passwords + ops bundle location).
Writes temp file, shreds on confirmation, clears screen.
Supports --reprint for re-delivery.
T04: ~/.claude/commands/creds-init.md — /creds-init skill replaces
/creds-bootstrap. Fully autonomous execution via the agent.
T05: Makefile — creds-agent-init, creds-agent-status,
creds-emergency-reprint targets.
T06: creds-rotate.sh — --non-interactive flag for agent-driven
rotation. Auto-confirms all gates; tracks last_rotated_<key>
in creds-state.yaml. LLDAP web UI step prints warning in
non-interactive mode.
T07: canon/standards/credential-management_v0.2.md — updated
standard: KeePassXC removed from operational path, agent
bootstrap as Phase 0, emergency bundle section, prohibited
patterns updated.
Also: creds-status.sh handles both schema v1 (legacy) and v2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,25 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# creds-rotate.sh — guided rotation for a single net-kingdom credential.
|
||||
# creds-rotate.sh — guided or non-interactive rotation for a single net-kingdom credential.
|
||||
#
|
||||
# Usage:
|
||||
# SECRET=<name> bash sso-mfa/bootstrap/creds-rotate.sh [secrets-dir]
|
||||
# make creds-rotate SECRET=<name>
|
||||
#
|
||||
# # Agent / non-interactive mode (NK-WP-0005):
|
||||
# SECRET=<name> bash sso-mfa/bootstrap/creds-rotate.sh --non-interactive [secrets-dir]
|
||||
#
|
||||
# The script:
|
||||
# 1. Validates the secret name
|
||||
# 2. Prints rotation impact and required coordination steps
|
||||
# 3. Generates a new value (same entropy as original)
|
||||
# 4. Guides through the atomic update sequence
|
||||
# 5. After confirmation, updates creds-state.yaml and reminds to re-bundle
|
||||
# 4. Guides through the atomic update sequence (interactive) or runs it
|
||||
# automatically (--non-interactive)
|
||||
# 5. After completion, updates creds-state.yaml
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SECRETS_DIR="${1:-$SCRIPT_DIR/secrets}"
|
||||
STATE_FILE="$SCRIPT_DIR/creds-state.yaml"
|
||||
K8S_DIR="$REPO_ROOT/sso-mfa/k8s"
|
||||
|
||||
NON_INTERACTIVE=false
|
||||
POSITIONAL_ARGS=()
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" == "--non-interactive" ]]; then
|
||||
NON_INTERACTIVE=true
|
||||
else
|
||||
POSITIONAL_ARGS+=("$arg")
|
||||
fi
|
||||
done
|
||||
|
||||
SECRETS_DIR="${POSITIONAL_ARGS[0]:-$SCRIPT_DIR/secrets}"
|
||||
SECRET="${SECRET:-}"
|
||||
|
||||
rnd_hex() { openssl rand -hex "$1"; }
|
||||
@@ -27,6 +41,10 @@ rnd_b64() { openssl rand -base64 "$1" | tr -d '\n/+=' | head -c "$2"; }
|
||||
|
||||
confirm() {
|
||||
local prompt="${1:-Continue?}"
|
||||
if [[ "$NON_INTERACTIVE" == true ]]; then
|
||||
echo " [non-interactive] auto-confirming: $prompt"
|
||||
return 0
|
||||
fi
|
||||
echo ""
|
||||
read -rp "$prompt [y/N] " ans
|
||||
[[ "${ans,,}" == "y" ]]
|
||||
@@ -35,16 +53,38 @@ confirm() {
|
||||
header() {
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " Rotating: $SECRET"
|
||||
echo " Rotating: $SECRET$([ "$NON_INTERACTIVE" = true ] && echo " (non-interactive)" || true)"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
}
|
||||
|
||||
update_last_rotated() {
|
||||
local key="$1"
|
||||
if [[ -f "$STATE_FILE" ]]; then
|
||||
# Update or append last_rotated_<key> in creds-state.yaml
|
||||
local ts
|
||||
ts="$(date -Iseconds)"
|
||||
if grep -qE "^last_rotated_${key}:" "$STATE_FILE"; then
|
||||
sed -i "s|^last_rotated_${key}: .*|last_rotated_${key}: \"${ts}\"|" "$STATE_FILE"
|
||||
else
|
||||
echo "last_rotated_${key}: \"${ts}\"" >> "$STATE_FILE"
|
||||
fi
|
||||
echo " [state] last_rotated_${key} → ${ts}"
|
||||
fi
|
||||
}
|
||||
|
||||
post_rotation_reminder() {
|
||||
echo ""
|
||||
echo "Post-rotation checklist:"
|
||||
echo " ✓ Update KeePassXC entry for this secret"
|
||||
echo " ✓ Run: make creds-bundle (refresh offsite backup)"
|
||||
echo " ✓ Run: make creds-verify (confirm cluster state)"
|
||||
if [[ "$NON_INTERACTIVE" == false ]]; then
|
||||
echo "Post-rotation checklist:"
|
||||
echo " ✓ Update your personal password manager entry for this secret"
|
||||
echo " ✓ Run: make creds-bundle (refresh offsite backup)"
|
||||
echo " ✓ Run: make creds-verify (confirm cluster state)"
|
||||
else
|
||||
echo "Post-rotation (agent mode):"
|
||||
echo " ✓ creds-state.yaml updated with last_rotated timestamp"
|
||||
echo " Run: make creds-bundle (refresh offsite backup)"
|
||||
echo " Run: make creds-verify (confirm cluster state)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Dispatch ──────────────────────────────────────────────────────────────────
|
||||
@@ -68,6 +108,7 @@ PI_SECRET_KEY)
|
||||
echo " 2. Restarting privacyIDEA pod..."
|
||||
kubectl rollout restart deployment privacyidea -n mfa
|
||||
kubectl rollout status deployment privacyidea -n mfa
|
||||
update_last_rotated "PI_SECRET_KEY"
|
||||
post_rotation_reminder
|
||||
;;
|
||||
|
||||
@@ -118,6 +159,7 @@ PI_DB_PASSWORD)
|
||||
echo " 3. Restarting privacyIDEA pod..."
|
||||
kubectl rollout restart deployment privacyidea -n mfa
|
||||
kubectl rollout status deployment privacyidea -n mfa
|
||||
update_last_rotated "PI_DB_PASSWORD"
|
||||
post_rotation_reminder
|
||||
;;
|
||||
|
||||
@@ -137,6 +179,7 @@ LLDAP_JWT_SECRET)
|
||||
echo " Restarting LLDAP pod..."
|
||||
kubectl rollout restart deployment lldap -n sso
|
||||
kubectl rollout status deployment lldap -n sso
|
||||
update_last_rotated "LLDAP_JWT_SECRET"
|
||||
post_rotation_reminder
|
||||
;;
|
||||
|
||||
@@ -158,8 +201,13 @@ LLDAP_LDAP_USER_PASS)
|
||||
[[ -f "$ENV_FILE" ]] && sed -i "s|^LLDAP_LDAP_USER_PASS=.*|LLDAP_LDAP_USER_PASS=$NEW_VAL|" "$ENV_FILE"
|
||||
echo " 1. Updating LLDAP admin password via API..."
|
||||
echo " WARN: Automated LLDAP password update not implemented."
|
||||
echo " Log in to https://lldap.coulomb.social and change the admin password manually."
|
||||
confirm "Confirm you have updated the LLDAP admin password?" || { echo "Aborting."; exit 1; }
|
||||
if [[ "$NON_INTERACTIVE" == false ]]; then
|
||||
echo " Log in to https://lldap.coulomb.social and change the admin password manually."
|
||||
confirm "Confirm you have updated the LLDAP admin password?" || { echo "Aborting."; exit 1; }
|
||||
else
|
||||
echo " [non-interactive] Skipping LLDAP web UI step — update manually:"
|
||||
echo " Log in to https://lldap.coulomb.social and set admin password to: $NEW_VAL"
|
||||
fi
|
||||
echo " 2. Updating Authelia secrets..."
|
||||
(cd "$K8S_DIR/authelia" && bash create-secrets.sh "$SECRETS_DIR")
|
||||
echo " 3. Updating KeyCape secrets..."
|
||||
@@ -169,6 +217,7 @@ LLDAP_LDAP_USER_PASS)
|
||||
kubectl rollout restart deployment keycape -n sso
|
||||
kubectl rollout status deployment authelia -n sso
|
||||
kubectl rollout status deployment keycape -n sso
|
||||
update_last_rotated "LLDAP_LDAP_USER_PASS"
|
||||
post_rotation_reminder
|
||||
;;
|
||||
|
||||
@@ -187,6 +236,7 @@ AUTHELIA_SESSION_SECRET)
|
||||
(cd "$K8S_DIR/authelia" && bash create-secrets.sh "$SECRETS_DIR")
|
||||
kubectl rollout restart deployment authelia -n sso
|
||||
kubectl rollout status deployment authelia -n sso
|
||||
update_last_rotated "AUTHELIA_SESSION_SECRET"
|
||||
post_rotation_reminder
|
||||
;;
|
||||
|
||||
@@ -214,6 +264,7 @@ AUTHELIA_KEYCAPE_CLIENT_SECRET)
|
||||
kubectl rollout restart deployment keycape -n sso
|
||||
kubectl rollout status deployment authelia -n sso
|
||||
kubectl rollout status deployment keycape -n sso
|
||||
update_last_rotated "AUTHELIA_KEYCAPE_CLIENT_SECRET"
|
||||
post_rotation_reminder
|
||||
;;
|
||||
|
||||
@@ -248,7 +299,12 @@ KEYCAPE_RSA_KEY)
|
||||
kubectl rollout restart deployment keycape -n sso
|
||||
kubectl rollout status deployment keycape -n sso
|
||||
echo ""
|
||||
echo " ✔ RSA key rotated. Store the new key in KeePassXC: net-kingdom/KeyCape/jwt-signing-key"
|
||||
if [[ "$NON_INTERACTIVE" == false ]]; then
|
||||
echo " ✔ RSA key rotated. Store the new key in your password manager: net-kingdom/KeyCape/jwt-signing-key"
|
||||
else
|
||||
echo " ✔ RSA key rotated."
|
||||
fi
|
||||
update_last_rotated "KEYCAPE_RSA_KEY"
|
||||
post_rotation_reminder
|
||||
;;
|
||||
|
||||
@@ -271,7 +327,10 @@ BREAKGLASS_PASSWORD)
|
||||
--from-literal=BREAKGLASS_PASSWORD="$NEW_VAL" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
echo ""
|
||||
echo " Update KeePassXC entry: net-kingdom/Break-glass/break-glass"
|
||||
if [[ "$NON_INTERACTIVE" == false ]]; then
|
||||
echo " Update your password manager: net-kingdom/Break-glass/break-glass"
|
||||
fi
|
||||
update_last_rotated "BREAKGLASS_PASSWORD"
|
||||
post_rotation_reminder
|
||||
;;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user