#!/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