generated from coulomb/repo-seed
162 lines
4.8 KiB
Bash
162 lines
4.8 KiB
Bash
#!/usr/bin/env bash
|
|
# refresh-pi-token-live.sh — refresh KeyCape's privacyIDEA admin token.
|
|
#
|
|
# This is an attended repair for "mfa check error" when the privacyIDEA admin
|
|
# JWT embedded in keycape-config has expired. It prompts for the pi-admin
|
|
# password, fetches a fresh token inside the privacyIDEA pod, updates the
|
|
# keycape-pi-token Secret, patches keycape-config without printing secrets, and
|
|
# restarts KeyCape.
|
|
#
|
|
# Usage:
|
|
# bash refresh-pi-token-live.sh [username]
|
|
#
|
|
# Optional environment:
|
|
# KUBECTL=/path/to/kubectl
|
|
# KEYCAPE_PI_REALM=coulomb|netkingdom
|
|
|
|
set -euo pipefail
|
|
|
|
USERNAME="${1:-platform-root}"
|
|
KUBECTL="${KUBECTL:-kubectl}"
|
|
SSO_NAMESPACE="${SSO_NAMESPACE:-sso}"
|
|
MFA_NAMESPACE="${MFA_NAMESPACE:-mfa}"
|
|
KEYCAPE_DEPLOYMENT="${KEYCAPE_DEPLOYMENT:-keycape}"
|
|
KEYCAPE_SECRET="${KEYCAPE_SECRET:-keycape-config}"
|
|
KEYCAPE_TOKEN_SECRET="${KEYCAPE_TOKEN_SECRET:-keycape-pi-token}"
|
|
|
|
if [[ -r /dev/tty ]]; then
|
|
printf "privacyIDEA pi-admin password: " > /dev/tty
|
|
IFS= read -r -s PI_ADMIN_PASSWORD < /dev/tty
|
|
printf "\n" > /dev/tty
|
|
else
|
|
printf "privacyIDEA pi-admin password: " >&2
|
|
IFS= read -r -s PI_ADMIN_PASSWORD
|
|
printf "\n" >&2
|
|
fi
|
|
if [[ -z "$PI_ADMIN_PASSWORD" ]]; then
|
|
echo "[FAIL] Empty pi-admin password." >&2
|
|
exit 1
|
|
fi
|
|
|
|
PI_POD="$(
|
|
"$KUBECTL" get pod -n "$MFA_NAMESPACE" \
|
|
-l app.kubernetes.io/name=privacyidea \
|
|
--field-selector=status.phase=Running \
|
|
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true
|
|
)"
|
|
if [[ -z "$PI_POD" ]]; then
|
|
echo "[FAIL] No running privacyIDEA pod in namespace $MFA_NAMESPACE." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Fetching fresh privacyIDEA admin token inside pod $PI_POD ..."
|
|
PI_TOKEN="$(
|
|
"$KUBECTL" exec -n "$MFA_NAMESPACE" "$PI_POD" -- \
|
|
env PI_ADMIN_PASSWORD="$PI_ADMIN_PASSWORD" \
|
|
python3 -c '
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
payload = urllib.parse.urlencode({
|
|
"username": "pi-admin",
|
|
"password": os.environ["PI_ADMIN_PASSWORD"],
|
|
}).encode()
|
|
req = urllib.request.Request("http://localhost:8080/auth", data=payload)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=10) as response:
|
|
body = json.load(response)
|
|
print(body["result"]["value"]["token"])
|
|
except Exception as exc:
|
|
print(str(exc), file=sys.stderr)
|
|
sys.exit(1)
|
|
' 2>/dev/null
|
|
)"
|
|
unset PI_ADMIN_PASSWORD
|
|
|
|
if [[ -z "$PI_TOKEN" ]]; then
|
|
echo "[FAIL] Could not fetch a privacyIDEA admin token." >&2
|
|
exit 1
|
|
fi
|
|
|
|
tmpdir="$(mktemp -d)"
|
|
cleanup() {
|
|
rm -rf "$tmpdir"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
current_config="$(
|
|
"$KUBECTL" get secret "$KEYCAPE_SECRET" -n "$SSO_NAMESPACE" \
|
|
-o jsonpath='{.data.config\.yaml}' | base64 -d
|
|
)"
|
|
current_realm="$(
|
|
CONFIG_YAML="$current_config" python3 -c '
|
|
import os
|
|
import re
|
|
|
|
match = re.search(r"(?m)^ realm:\s*[\"'\'']?([^\"'\'']+)", os.environ["CONFIG_YAML"])
|
|
print(match.group(1).strip() if match else "")
|
|
'
|
|
)"
|
|
|
|
selected_realm="${KEYCAPE_PI_REALM:-}"
|
|
if [[ -z "$selected_realm" && -n "$current_realm" ]]; then
|
|
selected_realm="$current_realm"
|
|
fi
|
|
if [[ -z "$selected_realm" ]]; then
|
|
selected_realm="coulomb"
|
|
fi
|
|
|
|
if [[ "$selected_realm" != "coulomb" && "$selected_realm" != "netkingdom" ]]; then
|
|
echo "[FAIL] Refusing unsupported privacyIDEA realm: $selected_realm" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Selected privacyIDEA realm for KeyCape: $selected_realm"
|
|
|
|
"$KUBECTL" get secret "$KEYCAPE_SECRET" -n "$SSO_NAMESPACE" \
|
|
-o jsonpath='{.data.key\.pem}' | base64 -d > "$tmpdir/key.pem"
|
|
chmod 600 "$tmpdir/key.pem"
|
|
|
|
CONFIG_YAML="$current_config" PI_TOKEN="$PI_TOKEN" PI_REALM="$selected_realm" \
|
|
python3 -c '
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
config = os.environ["CONFIG_YAML"]
|
|
token = json.dumps(os.environ["PI_TOKEN"])
|
|
realm = json.dumps(os.environ["PI_REALM"])
|
|
|
|
config, token_count = re.subn(r"(?m)^ adminToken:.*$", " adminToken: " + token, config)
|
|
config, realm_count = re.subn(r"(?m)^ realm:.*$", " realm: " + realm, config)
|
|
if token_count != 1 or realm_count != 1:
|
|
print("Could not patch exactly one adminToken and one realm field.", file=sys.stderr)
|
|
sys.exit(1)
|
|
sys.stdout.write(config)
|
|
' > "$tmpdir/config.yaml"
|
|
|
|
echo "Applying refreshed KeyCape config Secret ..."
|
|
"$KUBECTL" create secret generic "$KEYCAPE_TOKEN_SECRET" \
|
|
--namespace="$SSO_NAMESPACE" \
|
|
--from-literal=token="$PI_TOKEN" \
|
|
--dry-run=client -o yaml | "$KUBECTL" apply -f -
|
|
|
|
"$KUBECTL" create secret generic "$KEYCAPE_SECRET" \
|
|
--namespace="$SSO_NAMESPACE" \
|
|
--from-file=config.yaml="$tmpdir/config.yaml" \
|
|
--from-file=key.pem="$tmpdir/key.pem" \
|
|
--dry-run=client -o yaml | "$KUBECTL" apply -f -
|
|
|
|
unset PI_TOKEN
|
|
|
|
echo "Restarting KeyCape ..."
|
|
"$KUBECTL" rollout restart "deployment/$KEYCAPE_DEPLOYMENT" -n "$SSO_NAMESPACE"
|
|
"$KUBECTL" rollout status "deployment/$KEYCAPE_DEPLOYMENT" -n "$SSO_NAMESPACE" --timeout=90s
|
|
|
|
echo ""
|
|
echo "[OK] KeyCape privacyIDEA token refreshed. Retry the OIDC login with a fresh URL."
|