barycenter/scripts/validate-oidc.sh

171 lines
7.7 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
# validate-oidc.sh — Validate OIDC endpoints on a running Barycenter instance.
#
# Usage: ./scripts/validate-oidc.sh [BASE_URL]
# BASE_URL defaults to http://localhost:9090
set -euo pipefail
BASE_URL="${1:-http://localhost:9090}"
PASS=0
FAIL=0
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
check() {
local desc="$1" actual="$2" expected="$3"
if [ "$actual" = "$expected" ]; then
pass "$desc"
else
fail "$desc (expected '$expected', got '$actual')"
fi
}
echo "=== Barycenter OIDC Validation ==="
echo "Base URL: $BASE_URL"
echo
# ─── 1. Discovery ───────────────────────────────────────────────────────────
echo "--- 1. Discovery Document ---"
DISCO=$(curl -sf "$BASE_URL/.well-known/openid-configuration" 2>/dev/null) || {
fail "Discovery endpoint unreachable"
echo "Cannot continue without discovery. Is the server running?"
exit 1
}
check "issuer present" "$(echo "$DISCO" | jq -r '.issuer')" "$BASE_URL"
check "authorization_endpoint" "$(echo "$DISCO" | jq -r '.authorization_endpoint')" "$BASE_URL/authorize"
check "token_endpoint" "$(echo "$DISCO" | jq -r '.token_endpoint')" "$BASE_URL/token"
check "userinfo_endpoint" "$(echo "$DISCO" | jq -r '.userinfo_endpoint')" "$BASE_URL/userinfo"
check "jwks_uri" "$(echo "$DISCO" | jq -r '.jwks_uri')" "$BASE_URL/.well-known/jwks.json"
check "registration_endpoint" "$(echo "$DISCO" | jq -r '.registration_endpoint')" "$BASE_URL/connect/register"
check "revocation_endpoint" "$(echo "$DISCO" | jq -r '.revocation_endpoint')" "$BASE_URL/revoke"
check "introspection_endpoint" "$(echo "$DISCO" | jq -r '.introspection_endpoint')" "$BASE_URL/introspect"
# Check supported values
check "response_types includes code" \
"$(echo "$DISCO" | jq '[.response_types_supported[] | select(. == "code")] | length')" "1"
check "code_challenge_methods includes S256" \
"$(echo "$DISCO" | jq '[.code_challenge_methods_supported[] | select(. == "S256")] | length')" "1"
check "auth methods include client_secret_basic" \
"$(echo "$DISCO" | jq '[.token_endpoint_auth_methods_supported[] | select(. == "client_secret_basic")] | length')" "1"
check "auth methods include client_secret_post" \
"$(echo "$DISCO" | jq '[.token_endpoint_auth_methods_supported[] | select(. == "client_secret_post")] | length')" "1"
check "signing alg includes RS256" \
"$(echo "$DISCO" | jq '[.id_token_signing_alg_values_supported[] | select(. == "RS256")] | length')" "1"
echo
# ─── 2. JWKS ────────────────────────────────────────────────────────────────
echo "--- 2. JWKS ---"
JWKS=$(curl -sf "$BASE_URL/.well-known/jwks.json" 2>/dev/null) || {
fail "JWKS endpoint unreachable"
JWKS="{}"
}
KEY_COUNT=$(echo "$JWKS" | jq '.keys | length' 2>/dev/null || echo 0)
if [ "$KEY_COUNT" -gt 0 ]; then
pass "JWKS has $KEY_COUNT key(s)"
HAS_KID=$(echo "$JWKS" | jq -r '.keys[0].kid // empty')
if [ -n "$HAS_KID" ]; then
pass "First key has kid: $HAS_KID"
else
fail "First key missing kid"
fi
else
fail "JWKS has no keys"
fi
echo
# ─── 3. Dynamic Client Registration ─────────────────────────────────────────
echo "--- 3. Client Registration ---"
REG_RESP=$(curl -sf -X POST "$BASE_URL/connect/register" \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": ["http://localhost:8080/callback"],
"client_name": "Validation Script Client",
"token_endpoint_auth_method": "client_secret_basic"
}' 2>/dev/null) || {
fail "Client registration failed"
REG_RESP="{}"
}
CLIENT_ID=$(echo "$REG_RESP" | jq -r '.client_id // empty')
CLIENT_SECRET=$(echo "$REG_RESP" | jq -r '.client_secret // empty')
if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ]; then
pass "Client registered (client_id: ${CLIENT_ID:0:12}...)"
else
fail "Client registration did not return client_id/client_secret"
fi
echo
# ─── 4. PKCE Generation ─────────────────────────────────────────────────────
echo "--- 4. PKCE Generation ---"
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '+/' '-_')
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '+/' '-_')
pass "code_verifier generated (${#CODE_VERIFIER} chars)"
pass "code_challenge generated (S256)"
echo
# ─── 5. Authorization URL ───────────────────────────────────────────────────
echo "--- 5. Authorization ---"
AUTH_URL="$BASE_URL/authorize?client_id=$CLIENT_ID&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&response_type=code&scope=openid%20profile%20email&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=validate123&nonce=nonce456"
AUTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$AUTH_URL" 2>/dev/null)
# Expect 302 redirect (to login) or 200 (if already logged in)
if [ "$AUTH_STATUS" = "302" ] || [ "$AUTH_STATUS" = "303" ] || [ "$AUTH_STATUS" = "200" ]; then
pass "Authorization endpoint responds ($AUTH_STATUS)"
else
fail "Authorization endpoint returned $AUTH_STATUS"
fi
echo
# ─── 6. UserInfo (unauthenticated — expect 401) ─────────────────────────────
echo "--- 6. UserInfo (unauthenticated) ---"
UI_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/userinfo" 2>/dev/null)
check "UserInfo without token returns 401" "$UI_STATUS" "401"
echo
# ─── 7. Token Introspection (with invalid token) ────────────────────────────
echo "--- 7. Token Introspection ---"
if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ]; then
AUTH_HEADER=$(printf '%s:%s' "$CLIENT_ID" "$CLIENT_SECRET" | base64 -w0 2>/dev/null || printf '%s:%s' "$CLIENT_ID" "$CLIENT_SECRET" | base64 2>/dev/null)
INTROSPECT_RESP=$(curl -sf -X POST "$BASE_URL/introspect" \
-H "Authorization: Basic $AUTH_HEADER" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=invalid_token_12345" 2>/dev/null) || INTROSPECT_RESP="{}"
ACTIVE=$(echo "$INTROSPECT_RESP" | jq -r '.active // empty')
check "Introspection of invalid token returns active=false" "$ACTIVE" "false"
else
fail "Skipping introspection (no client credentials)"
fi
echo
# ─── 8. Token Revocation (with invalid token — should return 200) ───────────
echo "--- 8. Token Revocation ---"
if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ]; then
REVOKE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/revoke" \
-H "Authorization: Basic $AUTH_HEADER" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=invalid_token_12345" 2>/dev/null)
check "Revocation of unknown token returns 200" "$REVOKE_STATUS" "200"
else
fail "Skipping revocation (no client credentials)"
fi
echo
# ─── Summary ─────────────────────────────────────────────────────────────────
echo "=== Summary ==="
echo " Passed: $PASS"
echo " Failed: $FAIL"
echo
if [ "$FAIL" -gt 0 ]; then
echo "Some checks failed. Ensure the server is running and configured correctly."
exit 1
else
echo "All checks passed."
exit 0
fi