#!/usr/bin/env bash # End-to-end auth flow tests for api-gateway. # Validates: JWT auth, identity headers, token exchange, error handling. # # Usage: # ./scripts/test-auth-e2e.sh # # Requires: kubectl access to cluster, curl, jq set -euo pipefail API="https://api.riotpiao.com" TOKEN_URL="https://authentik.riotpiao.com/application/o/token/" PASS=0 FAIL=0 SKIP=0 # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' check() { local name="$1" expected="$2" actual="$3" if [ "$expected" = "$actual" ]; then echo -e " ${GREEN}✓${NC} $name" PASS=$((PASS + 1)) else echo -e " ${RED}✗${NC} $name (expected $expected, got $actual)" FAIL=$((FAIL + 1)) fi } skip() { echo -e " ${YELLOW}○${NC} $1 (skipped: $2)" SKIP=$((SKIP + 1)) } # --- Load credentials --- echo "Loading credentials from cluster..." P_SECRET=$(kubectl get secret -n portfolio portfolio-agent-oidc -o jsonpath='{.data.CLIENT_SECRET}' | base64 -d 2>/dev/null) || true M_SECRET=$(kubectl get secret -n poimen memory-agent-oidc -o jsonpath='{.data.CLIENT_SECRET}' | base64 -d 2>/dev/null) || true if [ -z "$P_SECRET" ] || [ -z "$M_SECRET" ]; then echo "ERROR: Cannot read service account secrets. Check kubectl context." exit 1 fi # --- Get tokens --- echo "" echo "=== Acquiring tokens ===" P_TOKEN=$(curl -s -X POST "$TOKEN_URL" \ -d "grant_type=client_credentials&client_id=portfolio-agent&client_secret=$P_SECRET&scope=openid roles" \ | jq -r '.access_token') M_TOKEN=$(curl -s -X POST "$TOKEN_URL" \ -d "grant_type=client_credentials&client_id=memory-agent&client_secret=$M_SECRET&scope=openid roles" \ | jq -r '.access_token') if [ "$P_TOKEN" = "null" ] || [ -z "$P_TOKEN" ]; then echo "ERROR: Failed to get portfolio-agent token" exit 1 fi echo " portfolio-agent: token acquired" echo " memory-agent: token acquired" # --- Scenario 1: LLM inference --- echo "" echo "=== Scenario 1: LLM inference (llm:inference) ===" CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/chat/completions" \ -H "Authorization: Bearer $P_TOKEN" -H "Content-Type: application/json" \ -d '{"model":"qwen2.5:3b-instruct","messages":[{"role":"user","content":"ok"}],"max_tokens":2}') check "portfolio-agent -> qwen chat" "200" "$CODE" CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/chat/completions" \ -H "Authorization: Bearer $M_TOKEN" -H "Content-Type: application/json" \ -d '{"model":"reasoning","messages":[{"role":"user","content":"ok"}],"max_tokens":2}') check "memory-agent -> reasoning chat" "200" "$CODE" CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/embeddings" \ -H "Authorization: Bearer $P_TOKEN" -H "Content-Type: application/json" \ -d '{"model":"nomic-ai/nomic-embed-text-v2-moe","input":"test"}') check "portfolio-agent -> embeddings" "200" "$CODE" CODE=$(curl -s -o /dev/null -w "%{http_code}" "$API/v1/models") check "GET /v1/models (no auth)" "200" "$CODE" # --- Scenario 2: Auth enforcement --- echo "" echo "=== Scenario 2: Auth enforcement ===" CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/chat/completions" \ -H "Content-Type: application/json" \ -d '{"model":"qwen2.5:3b-instruct","messages":[{"role":"user","content":"ok"}]}') check "no token -> 401" "401" "$CODE" CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/v1/chat/completions" \ -H "Authorization: Bearer garbage-token" -H "Content-Type: application/json" \ -d '{"model":"qwen2.5:3b-instruct","messages":[{"role":"user","content":"ok"}]}') check "bad token -> 403" "403" "$CODE" # --- Scenario 3: Token claims --- echo "" echo "=== Scenario 3: Token claims ===" decode_jwt() { python3 -c "import base64,json,sys; p=sys.argv[1].split('.')[1]; p+='='*(4-len(p)%4); print(json.dumps(json.loads(base64.urlsafe_b64decode(p))))" "$1"; } P_CLAIMS=$(decode_jwt "$P_TOKEN") P_ROLES=$(echo "$P_CLAIMS" | jq -r '.roles | join(",")') check "portfolio-agent has llm:inference" "true" "$(echo "$P_ROLES" | grep -q 'llm:inference' && echo true || echo false)" check "portfolio-agent has s3:read" "true" "$(echo "$P_ROLES" | grep -q 's3:read' && echo true || echo false)" check "portfolio-agent has sqs:read" "true" "$(echo "$P_ROLES" | grep -q 'sqs:read' && echo true || echo false)" M_CLAIMS=$(decode_jwt "$M_TOKEN") M_ROLES=$(echo "$M_CLAIMS" | jq -r '.roles | join(",")') check "memory-agent has memory:write" "true" "$(echo "$M_ROLES" | grep -q 'memory:write' && echo true || echo false)" check "memory-agent has s3:write" "true" "$(echo "$M_ROLES" | grep -q 's3:write' && echo true || echo false)" # --- Scenario 4: X-Service dispatch --- echo "" echo "=== Scenario 4: X-Service dispatch ===" CODE=$(curl -s -o /dev/null -w "%{http_code}" "$API/" \ -H "X-Service: s3" -H "X-Resource: list-objects" \ -H "Authorization: Bearer $P_TOKEN") check "portfolio-agent -> S3 list (reaches MinIO)" "403" "$CODE" # 403 = MinIO reached but rejects JWT (needs SigV4). Not a gateway auth issue. # Memory service might be down CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$API/" \ -H "X-Service: memory" -H "X-Resource: skills" \ -H "Authorization: Bearer $P_TOKEN" 2>/dev/null) if [ "$CODE" = "200" ]; then check "portfolio-agent -> memory skills" "200" "$CODE" elif [ "$CODE" = "502" ]; then skip "portfolio-agent -> memory skills" "poimen-memory pods down" else check "portfolio-agent -> memory skills" "200" "$CODE" fi # --- Scenario 5: Auth endpoints --- echo "" echo "=== Scenario 5: Auth endpoints ===" # /auth/token — may return 400 if password grant not working, but endpoint should respond CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/auth/token" \ -H "Content-Type: application/json" \ -d '{"username":"rock","password":"wrong"}') # Should get 400 (bad creds forwarded from Authentik) or 502 (upstream issue), not 404 if [ "$CODE" != "404" ] && [ "$CODE" != "405" ]; then check "/auth/token endpoint exists" "true" "true" else check "/auth/token endpoint exists" "not-404" "$CODE" fi CODE=$(curl -s -o /dev/null -w "%{http_code}" -X GET "$API/auth/token") check "GET /auth/token -> 405" "405" "$CODE" CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/auth/refresh" \ -H "Content-Type: application/json" \ -d '{"refresh_token":"invalid"}') if [ "$CODE" != "404" ] && [ "$CODE" != "405" ]; then check "/auth/refresh endpoint exists" "true" "true" else check "/auth/refresh endpoint exists" "not-404" "$CODE" fi # --- Summary --- echo "" echo "=========================================" TOTAL=$((PASS + FAIL + SKIP)) echo -e "Results: ${GREEN}$PASS passed${NC}, ${RED}$FAIL failed${NC}, ${YELLOW}$SKIP skipped${NC} / $TOTAL total" echo "=========================================" [ "$FAIL" -eq 0 ] && exit 0 || exit 1