generated from coulomb/repo-seed
- README.md: ipAllowList → ipWhiteList (match Traefik v2 fix) - verify-t04.sh: update success message (Keycloak → LLDAP+Authelia+KeyCape) - WORKPLAN.md: add full T04 section with deliverables, pending steps, done-criteria Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
218 lines
9.1 KiB
Bash
Executable File
218 lines
9.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# verify-t04.sh — verify NK-WP-0001-T04 done-criteria
|
|
#
|
|
# Checks:
|
|
# 1. privacyIDEA pod is Running in the mfa namespace
|
|
# 2. privacyidea Service exists
|
|
# 3. Traefik Middlewares exist
|
|
# 4. Ingress resources exist with correct hostnames
|
|
# 5. TLS certificates issued by cert-manager
|
|
# 6. Required K8s Secrets are present
|
|
# 7. PVCs are Bound
|
|
# 8. Enckey and auditkey Secrets present (from enckey-bootstrap.sh)
|
|
#
|
|
# Usage:
|
|
# chmod +x verify-t04.sh
|
|
# ./verify-t04.sh
|
|
|
|
set -euo pipefail
|
|
|
|
NAMESPACE="mfa"
|
|
PASS=0
|
|
FAIL=0
|
|
WARN=0
|
|
|
|
pass() { echo " [PASS] $1"; ((PASS++)); }
|
|
fail() { echo " [FAIL] $1"; ((FAIL++)); }
|
|
warn() { echo " [WARN] $1"; ((WARN++)); }
|
|
|
|
section() { echo ""; echo "── $1 ──────────────────────────────────────"; }
|
|
|
|
# ── 1. privacyIDEA pod ────────────────────────────────────────────────────────
|
|
section "1. privacyIDEA pod (namespace: $NAMESPACE)"
|
|
|
|
PI_POD=$(kubectl get pod -n "$NAMESPACE" \
|
|
-l app.kubernetes.io/name=privacyidea \
|
|
--field-selector=status.phase=Running \
|
|
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
|
|
|
if [[ -n "$PI_POD" ]]; then
|
|
pass "Pod Running: $PI_POD"
|
|
|
|
READY=$(kubectl get pod -n "$NAMESPACE" "$PI_POD" \
|
|
-o jsonpath='{.status.containerStatuses[0].ready}' 2>/dev/null || echo "false")
|
|
if [[ "$READY" == "true" ]]; then
|
|
pass "Pod readiness probe passing"
|
|
else
|
|
fail "Pod is Running but not Ready (probe failing — check logs)"
|
|
fi
|
|
else
|
|
PENDING=$(kubectl get pod -n "$NAMESPACE" \
|
|
-l app.kubernetes.io/name=privacyidea \
|
|
-o name 2>/dev/null | wc -l || echo 0)
|
|
if [[ "$PENDING" -gt 0 ]]; then
|
|
fail "privacyIDEA pod(s) exist but none are Running (check kubectl describe pod -n $NAMESPACE)"
|
|
else
|
|
fail "No privacyIDEA pods found in namespace $NAMESPACE — apply deployment.yaml"
|
|
fi
|
|
fi
|
|
|
|
# ── 2. Service ────────────────────────────────────────────────────────────────
|
|
section "2. Service"
|
|
|
|
if kubectl get service privacyidea -n "$NAMESPACE" &>/dev/null; then
|
|
pass "Service privacyidea exists"
|
|
PORT=$(kubectl get service privacyidea -n "$NAMESPACE" \
|
|
-o jsonpath='{.spec.ports[0].port}' 2>/dev/null || echo "")
|
|
if [[ "$PORT" == "8080" ]]; then
|
|
pass "Service port: 8080"
|
|
else
|
|
warn "Service port is $PORT (expected 8080 — check container port and netpol)"
|
|
fi
|
|
else
|
|
fail "Service privacyidea not found in namespace $NAMESPACE"
|
|
fi
|
|
|
|
# ── 3. Traefik Middlewares ────────────────────────────────────────────────────
|
|
section "3. Traefik Middlewares"
|
|
|
|
for mw in privacyidea-rate-limit privacyidea-admin-allowlist; do
|
|
if kubectl get middleware "$mw" -n "$NAMESPACE" &>/dev/null; then
|
|
pass "Middleware $mw exists"
|
|
else
|
|
fail "Middleware $mw not found — apply middleware.yaml"
|
|
fi
|
|
done
|
|
|
|
# ── 4. Ingress resources ──────────────────────────────────────────────────────
|
|
section "4. Ingress resources"
|
|
|
|
for ing in privacyidea privacyidea-admin privacyidea-account; do
|
|
if kubectl get ingress "$ing" -n "$NAMESPACE" &>/dev/null; then
|
|
pass "Ingress $ing exists"
|
|
else
|
|
fail "Ingress $ing not found — apply ingress.yaml"
|
|
fi
|
|
done
|
|
|
|
# Verify hostnames in the main Ingress
|
|
PI_HOST=$(kubectl get ingress privacyidea -n "$NAMESPACE" \
|
|
-o jsonpath='{.spec.rules[0].host}' 2>/dev/null || echo "")
|
|
if [[ "$PI_HOST" == "pink.coulomb.social" ]]; then
|
|
pass "Ingress host: pink.coulomb.social"
|
|
else
|
|
fail "Ingress host is '$PI_HOST' (expected pink.coulomb.social)"
|
|
fi
|
|
|
|
ACCOUNT_HOST=$(kubectl get ingress privacyidea-account -n "$NAMESPACE" \
|
|
-o jsonpath='{.spec.rules[0].host}' 2>/dev/null || echo "")
|
|
if [[ "$ACCOUNT_HOST" == "pink-account.coulomb.social" ]]; then
|
|
pass "Self-service host: pink-account.coulomb.social"
|
|
else
|
|
fail "Self-service host is '$ACCOUNT_HOST' (expected pink-account.coulomb.social)"
|
|
fi
|
|
|
|
# ── 5. TLS certificates ───────────────────────────────────────────────────────
|
|
section "5. TLS certificates"
|
|
|
|
for cert_secret in pink-tls pink-account-tls; do
|
|
if kubectl get secret "$cert_secret" -n "$NAMESPACE" &>/dev/null; then
|
|
# Check cert-manager Ready condition
|
|
CERT_NAME="${cert_secret%-tls}"
|
|
CERT_READY=$(kubectl get certificate "$CERT_NAME" -n "$NAMESPACE" \
|
|
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "")
|
|
if [[ "$CERT_READY" == "True" ]]; then
|
|
pass "Certificate $cert_name is Ready (TLS secret $cert_secret exists)"
|
|
else
|
|
warn "TLS secret $cert_secret exists but certificate status is not Ready (DNS propagation pending?)"
|
|
fi
|
|
else
|
|
warn "TLS secret $cert_secret not yet issued (cert-manager pending — check DNS and ACME)"
|
|
fi
|
|
done
|
|
|
|
# ── 6. K8s Secrets ────────────────────────────────────────────────────────────
|
|
section "6. K8s Secrets (namespace: $NAMESPACE)"
|
|
|
|
for secret in privacyidea-config; do
|
|
if kubectl get secret "$secret" -n "$NAMESPACE" &>/dev/null; then
|
|
pass "Secret $secret exists"
|
|
else
|
|
fail "Secret $secret not found — run create-secrets.sh"
|
|
fi
|
|
done
|
|
|
|
# DR secrets — created by enckey-bootstrap.sh (may not exist yet on first run)
|
|
for secret in privacyidea-enckey privacyidea-auditkeys privacyidea-trigger-admin; do
|
|
if kubectl get secret "$secret" -n "$NAMESPACE" &>/dev/null; then
|
|
pass "Secret $secret exists"
|
|
else
|
|
warn "Secret $secret not found — run enckey-bootstrap.sh / bootstrap-admin.sh"
|
|
fi
|
|
done
|
|
|
|
# ── 7. PVCs ───────────────────────────────────────────────────────────────────
|
|
section "7. PersistentVolumeClaims"
|
|
|
|
for pvc in privacyidea-data privacyidea-logs; do
|
|
STATUS=$(kubectl get pvc "$pvc" -n "$NAMESPACE" \
|
|
-o jsonpath='{.status.phase}' 2>/dev/null || echo "not found")
|
|
if [[ "$STATUS" == "Bound" ]]; then
|
|
pass "PVC $pvc: Bound"
|
|
elif [[ "$STATUS" == "not found" ]]; then
|
|
fail "PVC $pvc not found — apply pvc.yaml"
|
|
else
|
|
fail "PVC $pvc status: $STATUS (expected Bound)"
|
|
fi
|
|
done
|
|
|
|
# ── 8. Key material check ─────────────────────────────────────────────────────
|
|
section "8. Key material (inside pod)"
|
|
|
|
if [[ -n "$PI_POD" ]]; then
|
|
if kubectl exec -n "$NAMESPACE" "$PI_POD" -- test -f /etc/privacyidea/enckey 2>/dev/null; then
|
|
pass "enckey present in pod"
|
|
else
|
|
warn "enckey not found in pod — run enckey-bootstrap.sh (or wait for PI to generate on first DB init)"
|
|
fi
|
|
|
|
if kubectl exec -n "$NAMESPACE" "$PI_POD" -- test -f /etc/privacyidea/private.pem 2>/dev/null; then
|
|
pass "audit private.pem present in pod"
|
|
else
|
|
warn "audit private.pem not found — run enckey-bootstrap.sh"
|
|
fi
|
|
|
|
if kubectl exec -n "$NAMESPACE" "$PI_POD" -- \
|
|
pi-manage admin list 2>/dev/null | grep -q "pi-admin"; then
|
|
pass "Admin user pi-admin exists"
|
|
else
|
|
warn "pi-admin not found — run bootstrap-admin.sh"
|
|
fi
|
|
|
|
if kubectl exec -n "$NAMESPACE" "$PI_POD" -- \
|
|
pi-manage admin list 2>/dev/null | grep -q "trigger-admin"; then
|
|
pass "Admin user trigger-admin exists"
|
|
else
|
|
warn "trigger-admin not found — run bootstrap-admin.sh"
|
|
fi
|
|
else
|
|
warn "Skipping key material checks — no running pod"
|
|
fi
|
|
|
|
# ── Summary ───────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "════════════════════════════════════════════════════════════"
|
|
echo " T04 verification: PASS=$PASS WARN=$WARN FAIL=$FAIL"
|
|
echo "════════════════════════════════════════════════════════════"
|
|
|
|
if [[ "$FAIL" -gt 0 ]]; then
|
|
echo " Result: INCOMPLETE — resolve FAIL items before proceeding to T05"
|
|
exit 1
|
|
elif [[ "$WARN" -gt 0 ]]; then
|
|
echo " Result: PARTIAL — T04 core is up; WARN items should be resolved before T05"
|
|
exit 0
|
|
else
|
|
echo " Result: COMPLETE — T04 done-criteria met; proceed to T05 (SSO core: LLDAP+Authelia+KeyCape)"
|
|
exit 0
|
|
fi
|