11#! /usr/bin/env bash
2- # test-lab-18-04.sh — Lab 18-04: SSO Integration
3- # Module 18: Traefik reverse proxy and load balancer
4- # traefik with Keycloak OIDC/SAML authentication
2+ # test-lab-18-04.sh — Traefik Lab 04: ForwardAuth SSO via Keycloak OIDC
3+ # Tests: Keycloak setup, OIDC, oauth2-proxy ForwardAuth, public vs protected routes
54set -euo pipefail
65
7- LAB_ID=" 18-04"
8- LAB_NAME=" SSO Integration"
9- MODULE=" traefik"
10- COMPOSE_FILE=" docker/docker-compose.sso.yml"
11- PASS=0
12- FAIL=0
6+ PASS=0; FAIL=0
7+ KC_PASS=" ${KC_PASS:- Lab04Password! } "
8+ KC_URL=" http://localhost:8080"
9+ REALM=" it-stack"
10+ TRAEFIK_URL=" http://localhost:80"
1311
14- # ── Colors ────────────────────────────────────────────────────────────────────
15- RED=' \033[0;31m' ; GREEN=' \033[0;32m' ; YELLOW=' \033[1;33m'
16- CYAN=' \033[0;36m' ; NC=' \033[0m'
12+ pass () { (( ++ PASS)) ; echo " [PASS] $1 " ; }
13+ fail () { (( ++ FAIL)) ; echo " [FAIL] $1 " ; }
14+ warn () { echo " [WARN] $1 " ; }
15+ header (){ echo ; echo " === $1 ===" ; }
1716
18- pass () { echo -e " ${GREEN} [PASS]${NC} $1 " ; (( PASS++ )) ; }
19- fail () { echo -e " ${RED} [FAIL]${NC} $1 " ; (( FAIL++ )) ; }
20- info () { echo -e " ${CYAN} [INFO]${NC} $1 " ; }
21- warn () { echo -e " ${YELLOW} [WARN]${NC} $1 " ; }
17+ kc_token () {
18+ curl -sf -X POST " $KC_URL /realms/master/protocol/openid-connect/token" \
19+ -d " client_id=admin-cli&grant_type=password&username=admin&password=${KC_PASS} " \
20+ | grep -o ' "access_token":"[^"]*"' | cut -d' "' -f4
21+ }
2222
23- echo -e " ${CYAN} ======================================${NC} "
24- echo -e " ${CYAN} Lab ${LAB_ID} : ${LAB_NAME}${NC} "
25- echo -e " ${CYAN} Module: ${MODULE}${NC} "
26- echo -e " ${CYAN} ======================================${NC} "
27- echo " "
23+ header " 1. Keycloak Health"
24+ if curl -sf " $KC_URL /health/ready" | grep -q ' "status":"UP"' ; then
25+ pass " Keycloak /health/ready UP"
26+ else
27+ fail " Keycloak not ready" ; exit 1
28+ fi
29+
30+ header " 2. Admin Auth + Realm/Client/User Setup"
31+ TOKEN=$( kc_token)
32+ [[ -n " $TOKEN " ]] && pass " Admin token from master realm" || { fail " Admin auth failed" ; exit 1; }
33+ curl -sf -X POST " $KC_URL /admin/realms" \
34+ -H " Authorization: Bearer $TOKEN " -H " Content-Type: application/json" \
35+ -d " {\" realm\" :\" $REALM \" ,\" enabled\" :true}" -o /dev/null && pass " Realm '$REALM ' created" || warn " Realm may exist"
36+ TOKEN=$( kc_token)
37+ curl -sf -X POST " $KC_URL /admin/realms/$REALM /clients" \
38+ -H " Authorization: Bearer $TOKEN " -H " Content-Type: application/json" \
39+ -d " {\" clientId\" :\" oauth2-proxy\" ,\" secret\" :\" $KC_PASS \" ,\" publicClient\" :false,
40+ \" serviceAccountsEnabled\" :true,\" redirectUris\" :[\" http://localhost:4180/*\" ],
41+ \" enabled\" :true}" -o /dev/null && pass " oauth2-proxy client created" || warn " Client may exist"
42+ TOKEN=$( kc_token)
43+ STATUS=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $KC_URL /admin/realms/$REALM /users" \
44+ -H " Authorization: Bearer $TOKEN " -H " Content-Type: application/json" \
45+ -d " {\" username\" :\" labuser\" ,\" enabled\" :true,\" email\" :\" labuser@lab.local\" ,
46+ \" emailVerified\" :true,
47+ \" credentials\" :[{\" type\" :\" password\" ,\" value\" :\" $KC_PASS \" ,\" temporary\" :false}]}" )
48+ [[ " $STATUS " =~ ^(201| 409)$ ]] && pass " User 'labuser' ready" || fail " User creation failed (HTTP $STATUS )"
2849
29- # ── PHASE 1: Setup ────────────────────────────────────────────────────────────
30- info " Phase 1: Setup "
31- docker compose -f " ${COMPOSE_FILE} " up -d
32- info " Waiting 30s for ${MODULE} to initialize... "
33- sleep 30
50+ header " 3. OIDC Discovery "
51+ DISCOVERY= $( curl -sf " $KC_URL /realms/ $REALM /.well-known/openid-configuration " )
52+ for field in token_endpoint authorization_endpoint jwks_uri ; do
53+ echo " $DISCOVERY " | grep -q " \" $field \" " && pass " Discovery: $field present " || fail " Discovery missing $field "
54+ done
3455
35- # ── PHASE 2: Health Checks ────────────────────────────────────────────────────
36- info " Phase 2: Health Checks"
56+ header " 4. Client Credentials Token"
57+ SA_TOKEN=$( curl -sf -X POST " $KC_URL /realms/$REALM /protocol/openid-connect/token" \
58+ -d " client_id=oauth2-proxy&client_secret=${KC_PASS} &grant_type=client_credentials" \
59+ | grep -o ' "access_token":"[^"]*"' | cut -d' "' -f4)
60+ [[ -n " $SA_TOKEN " ]] && pass " Client credentials token obtained" || fail " Client credentials failed"
61+ IFS=' .' read -ra P <<< " $SA_TOKEN"
62+ [[ " ${# P[@]} " -eq 3 ]] && pass " JWT structure valid" || fail " Invalid JWT"
3763
38- if docker compose -f " ${COMPOSE_FILE} " ps | grep -q " running\|Up" ; then
39- pass " Container is running"
64+ header " 5. Traefik Dashboard"
65+ if curl -sf http://localhost:8080/api/version | grep -q " Version" ; then
66+ pass " Traefik dashboard API accessible"
4067else
41- fail " Container is not running "
68+ fail " Traefik dashboard not accessible "
4269fi
4370
44- # ── PHASE 3: Functional Tests ─────────────────────────────────────────────────
45- info " Phase 3: Functional Tests (Lab 04 — SSO Integration)"
71+ header " 6. Public Route (no auth required)"
72+ PUB=$( curl -s -o /dev/null -w " %{http_code}" " $TRAEFIK_URL /public" )
73+ [[ " $PUB " -eq 200 ]] && pass " Public route /public → 200 OK" || fail " Public route failed (HTTP $PUB )"
4674
47- # TODO: Add module-specific functional tests here
48- # Example:
49- # if curl -sf http://localhost:80/health > /dev/null 2>&1 ; then
50- # pass "Health endpoint responds "
51- # else
52- # fail "Health endpoint not reachable "
53- # fi
75+ header " 7. Protected Route (ForwardAuth — expect SSO redirect) "
76+ PROT= $( curl -s -o /dev/null -w " %{http_code} " --max-redirect 0 " $TRAEFIK_URL /protected " 2> /dev/null || true )
77+ if [[ " $PROT " =~ ^(302 | 307 | 401)$ ]] ; then
78+ pass " Protected route /protected → HTTP $PROT (ForwardAuth intercepted) "
79+ else
80+ fail " Protected route unexpected response: HTTP $PROT "
81+ fi
5482
55- warn " Functional tests for Lab 18-04 pending implementation"
83+ header " 8. oauth2-proxy /oauth2/callback Reachable via Traefik"
84+ OAUTH_STATUS=$( curl -s -o /dev/null -w " %{http_code}" --max-redirect 0 " $TRAEFIK_URL /oauth2/callback" 2> /dev/null || true)
85+ [[ " $OAUTH_STATUS " =~ ^(200| 302| 400)$ ]] && pass " /oauth2/callback route accessible (HTTP $OAUTH_STATUS )" \
86+ || fail " /oauth2/callback not accessible (HTTP $OAUTH_STATUS )"
5687
57- # ── PHASE 4: Cleanup ──────────────────────────────────────────────────────────
58- info " Phase 4: Cleanup "
59- docker compose -f " ${COMPOSE_FILE} " down -v --remove-orphans
60- info " Cleanup complete "
88+ header " 9. ForwardAuth Middleware in Traefik Config "
89+ ROUTERS= $( curl -sf http://localhost:8080/api/http/routers 2> /dev/null || echo " [] " )
90+ echo " $ROUTERS " | grep -q " forward-auth\|forwardauth\|oauth2 " \
91+ && pass " ForwardAuth middleware referenced in router config " || warn " ForwardAuth middleware not visible in API (may use labels only) "
6192
62- # ── Results ───────────────────────────────────────────────────────────────────
63- echo " "
64- echo -e " ${CYAN} ======================================${NC} "
65- echo -e " Lab ${LAB_ID} Complete"
66- echo -e " ${GREEN} PASS: ${PASS}${NC} | ${RED} FAIL: ${FAIL}${NC} "
67- echo -e " ${CYAN} ======================================${NC} "
93+ header " 10. Traefik Router Count"
94+ ROUTER_COUNT=$( echo " $ROUTERS " | grep -o ' "routerName"' | wc -l || echo " 0" )
95+ # Count routers differently
96+ ROUTER_COUNT=$( curl -sf http://localhost:8080/api/http/routers 2> /dev/null | grep -o ' "provider"' | wc -l || echo " 0" )
97+ [[ " $ROUTER_COUNT " -ge 2 ]] && pass " Traefik has $ROUTER_COUNT routers (public + protected expected)" || warn " Router count: $ROUTER_COUNT "
6898
69- if [ " ${FAIL} " -gt 0 ]; then
70- exit 1
71- fi
99+ echo
100+ echo " ═══════════════════════════════════════"
101+ echo " Lab 18-04 Results: $PASS passed, $FAIL failed"
102+ echo " ═══════════════════════════════════════"
103+ [[ " $FAIL " -eq 0 ]]
0 commit comments