Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
371ab10252 |
Executable
+178
@@ -0,0 +1,178 @@
|
|||||||
|
#!/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
|
||||||
Reference in New Issue
Block a user