#!/usr/bin/env bash # verify-t05.sh — verify NK-WP-0001-T05 done-criteria # # Checks all three components of the new SSO stack (LLDAP, Authelia, KeyCape). # # Sections: # 1. LLDAP pod Running+Ready # 2. LLDAP services (ldap :3890, web-ui :17170) # 3. LLDAP Middleware (lldap-admin-allowlist) # 4. LLDAP Ingress + hostname (lldap.coulomb.social) # 5. LLDAP TLS certificate # 6. LLDAP Secret (lldap-secrets) # 7. LLDAP PVC Bound # 8. LLDAP health endpoint # 9. Authelia pod Running+Ready # 10. Authelia service # 11. Authelia Ingress + hostname (auth.coulomb.social) # 12. Authelia TLS certificate # 13. Authelia Secrets (authelia-secrets) # 14. Authelia PVC Bound # 15. Authelia health endpoint # 16. KeyCape pod Running+Ready # 17. KeyCape service # 18. KeyCape Middlewares (keycape-rate-limit, keycape-hsts) # 19. KeyCape Ingress + hostname (kc.coulomb.social) # 20. KeyCape TLS certificate # 21. KeyCape Secret (keycape-config) # 22. KeyCape health endpoint (/healthz) # 23. KeyCape OIDC discovery (/.well-known/openid-configuration) # # Usage: # chmod +x verify-t05.sh # ./verify-t05.sh set -euo pipefail NAMESPACE="sso" LLDAP_HOST="lldap.coulomb.social" AUTH_HOST="auth.coulomb.social" KC_HOST="kc.coulomb.social" 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 ──────────────────────────────────────"; } check_pod() { local name="$1" local label="$2" local pod="" pod=$(kubectl get pod -n "$NAMESPACE" \ -l "app.kubernetes.io/name=${label}" \ --field-selector=status.phase=Running \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "") if [[ -n "$pod" ]]; then pass "Pod Running: $pod" local ready ready=$(kubectl get pod -n "$NAMESPACE" "$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 echo "$pod" else local count count=$(kubectl get pod -n "$NAMESPACE" \ -l "app.kubernetes.io/name=${label}" -o name 2>/dev/null | wc -l || echo 0) if [[ "$count" -gt 0 ]]; then fail "${name} pod(s) exist but none Running (kubectl describe pod -n $NAMESPACE -l app.kubernetes.io/name=${label})" else fail "No ${name} pods found — apply deployment.yaml" fi echo "" fi } check_service() { local svc="$1"; shift local ports=("$@") if kubectl get service "$svc" -n "$NAMESPACE" &>/dev/null; then pass "Service $svc exists" for port in "${ports[@]}"; do if kubectl get service "$svc" -n "$NAMESPACE" \ -o jsonpath="{.spec.ports[*].port}" 2>/dev/null | grep -qw "$port"; then pass "Service $svc has port $port" else fail "Service $svc missing port $port" fi done else fail "Service $svc not found" fi } check_ingress() { local ing="$1"; local expected_host="$2" if kubectl get ingress "$ing" -n "$NAMESPACE" &>/dev/null; then pass "Ingress $ing exists" local host host=$(kubectl get ingress "$ing" -n "$NAMESPACE" \ -o jsonpath='{.spec.rules[0].host}' 2>/dev/null || echo "") if [[ "$host" == "$expected_host" ]]; then pass "Ingress $ing host: $expected_host" else fail "Ingress $ing host is '$host' (expected $expected_host)" fi else fail "Ingress $ing not found — apply ingress.yaml" fi } check_tls() { local secret="$1"; local host="$2" if kubectl get secret "$secret" -n "$NAMESPACE" &>/dev/null; then local cert_name="${secret%-tls}" local cert_ready 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 Ready (TLS secret $secret issued)" else warn "TLS secret $secret exists but cert status not Ready (DNS/ACME pending?)" fi else warn "TLS secret $secret not yet issued (cert-manager pending — check DNS and ACME)" fi } check_secret() { local secret="$1" 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 } check_pvc() { local pvc="$1" local status 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 } # ═══════════════════════════════════════════════════════════════════ # LLDAP # ═══════════════════════════════════════════════════════════════════ section "1. LLDAP pod (namespace: $NAMESPACE)" LLDAP_POD=$(check_pod "LLDAP" "lldap") section "2. LLDAP services" check_service "lldap" 3890 17170 section "3. LLDAP Middleware" if kubectl get middleware lldap-admin-allowlist -n "$NAMESPACE" &>/dev/null; then pass "Middleware lldap-admin-allowlist exists" else fail "Middleware lldap-admin-allowlist not found — apply middleware.yaml" fi section "4. LLDAP Ingress" check_ingress "lldap" "$LLDAP_HOST" section "5. LLDAP TLS certificate" check_tls "lldap-tls" "$LLDAP_HOST" section "6. LLDAP Secret" check_secret "lldap-secrets" section "7. LLDAP PVC" check_pvc "lldap-data" section "8. LLDAP health endpoint" if [[ -n "$LLDAP_POD" ]]; then HTTP=$(kubectl exec -n "$NAMESPACE" "$LLDAP_POD" -- \ wget -qO- http://localhost:17170/health 2>/dev/null || echo "") if echo "$HTTP" | grep -qi "ok\|healthy\|true"; then pass "LLDAP health endpoint responds OK" else warn "LLDAP health endpoint response unclear: '$HTTP'" fi else warn "Skipping LLDAP health check — no running pod" fi # ═══════════════════════════════════════════════════════════════════ # Authelia # ═══════════════════════════════════════════════════════════════════ section "9. Authelia pod (namespace: $NAMESPACE)" AUTHELIA_POD=$(check_pod "Authelia" "authelia") section "10. Authelia service" check_service "authelia" 9091 section "11. Authelia Ingress" check_ingress "authelia" "$AUTH_HOST" section "12. Authelia TLS certificate" check_tls "auth-tls" "$AUTH_HOST" section "13. Authelia Secrets" check_secret "authelia-secrets" section "14. Authelia PVC" check_pvc "authelia-data" section "15. Authelia health endpoint" if [[ -n "$AUTHELIA_POD" ]]; then STATUS=$(kubectl exec -n "$NAMESPACE" "$AUTHELIA_POD" -- \ wget -qO- http://localhost:9091/api/health 2>/dev/null || echo "") if echo "$STATUS" | grep -qi "ok\|healthy"; then pass "Authelia /api/health responds OK" else warn "Authelia /api/health response unclear: '$STATUS'" fi else warn "Skipping Authelia health check — no running pod" fi # ═══════════════════════════════════════════════════════════════════ # KeyCape # ═══════════════════════════════════════════════════════════════════ section "16. KeyCape pod (namespace: $NAMESPACE)" KC_POD=$(check_pod "KeyCape" "keycape") section "17. KeyCape service" check_service "keycape" 8080 section "18. KeyCape Middlewares" for mw in keycape-rate-limit keycape-hsts; 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 section "19. KeyCape Ingress" check_ingress "keycape" "$KC_HOST" section "20. KeyCape TLS certificate" check_tls "kc-tls" "$KC_HOST" section "21. KeyCape Secrets" check_secret "keycape-config" section "22. KeyCape health endpoint" if [[ -n "$KC_POD" ]]; then STATUS=$(kubectl exec -n "$NAMESPACE" "$KC_POD" -- \ wget -qO- http://localhost:8080/healthz 2>/dev/null || echo "") if echo "$STATUS" | grep -qi "ok\|healthy"; then pass "KeyCape /healthz responds OK" else warn "KeyCape /healthz response unclear: '$STATUS'" fi else warn "Skipping KeyCape health check — no running pod" fi section "23. KeyCape OIDC discovery" if [[ -n "$KC_POD" ]]; then DISCOVERY=$(kubectl exec -n "$NAMESPACE" "$KC_POD" -- \ wget -qO- "http://localhost:8080/.well-known/openid-configuration" 2>/dev/null || echo "") if echo "$DISCOVERY" | grep -q '"issuer"'; then pass "OIDC discovery endpoint returns issuer" ISSUER=$(echo "$DISCOVERY" | python3 -c \ "import sys,json; d=json.load(sys.stdin); print(d.get('issuer',''))" 2>/dev/null || echo "") if [[ "$ISSUER" == "https://$KC_HOST" ]]; then pass "OIDC issuer matches CP-NK-004: $ISSUER" else warn "OIDC issuer is '$ISSUER' (expected https://$KC_HOST)" fi else fail "OIDC discovery endpoint did not return expected JSON" fi else warn "Skipping OIDC discovery check — no running pod" fi # ═══════════════════════════════════════════════════════════════════ # Summary # ═══════════════════════════════════════════════════════════════════ echo "" echo "════════════════════════════════════════════════════════════" echo " T05 verification: PASS=$PASS WARN=$WARN FAIL=$FAIL" echo "════════════════════════════════════════════════════════════" if [[ "$FAIL" -gt 0 ]]; then echo " Result: INCOMPLETE — resolve FAIL items before proceeding to T06" exit 1 elif [[ "$WARN" -gt 0 ]]; then echo " Result: PARTIAL — T05 core is up; WARN items should be resolved before T06" exit 0 else echo " Result: COMPLETE — T05 done-criteria met; proceed to T06 (MFA flow integration)" exit 0 fi