11#! /usr/bin/env bash
2- # test-lab-01-04.sh — Lab 01-04: SSO Integration
3- # Module 01: FreeIPA LDAP/Kerberos identity provider
4- # freeipa with Keycloak OIDC/SAML authentication
2+ # test-lab-01-04.sh — FreeIPA Lab 04: Keycloak LDAP Federation with FreeIPA
3+ # Tests: FreeIPA LDAP reachable, Keycloak federation component, user sync,
4+ # OIDC discovery, FreeIPA user visible in Keycloak after sync
5+ # NOTE: Requires privileged container — full test runs on real VMs.
6+ # CI runs syntax check + ShellCheck only.
57set -euo pipefail
68
7- LAB_ID=" 01-04"
8- LAB_NAME=" SSO Integration"
9- MODULE=" freeipa"
10- COMPOSE_FILE=" docker/docker-compose.sso.yml"
11- PASS=0
12- FAIL=0
9+ PASS=0; FAIL=0
10+ KC_PASS=" ${KC_PASS:- Lab04Password! } "
11+ KC_URL=" http://localhost:8080"
12+ IPA_HOST=" localhost"
13+ REALM=" it-stack"
1314
14- # ── Colors ────────────────────────────────────────────────────────────────────
15- RED=' \033[0;31m' ; GREEN=' \033[0;32m' ; YELLOW=' \033[1;33m'
16- CYAN=' \033[0;36m' ; NC=' \033[0m'
15+ pass () { (( ++ PASS)) ; echo " [PASS] $1 " ; }
16+ fail () { (( ++ FAIL)) ; echo " [FAIL] $1 " ; }
17+ warn () { echo " [WARN] $1 " ; }
18+ header (){ echo ; echo " === $1 ===" ; }
1719
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 " ; }
20+ kc_token () {
21+ curl -sf -X POST " $KC_URL /realms/master/protocol/openid-connect/token" \
22+ -d " client_id=admin-cli&grant_type=password&username=admin&password=${KC_PASS} " \
23+ | grep -o ' "access_token":"[^"]*"' | cut -d' "' -f4
24+ }
2225
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 " "
26+ header " 1. FreeIPA LDAP Port Reachable"
27+ if nc -z -w 5 " $IPA_HOST " 389 2> /dev/null; then
28+ pass " FreeIPA LDAP port 389 reachable"
29+ else
30+ fail " FreeIPA LDAP port 389 not reachable"
31+ fi
2832
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
33+ header " 2. FreeIPA Anonymous LDAP Bind"
34+ if ldapsearch -x -H " ldap://$IPA_HOST :389" -b " dc=lab,dc=local" " (objectClass=organizationalUnit)" dn 2> /dev/null | grep -q " numEntries" ; then
35+ pass " Anonymous LDAP query returns OUs"
36+ else
37+ warn " Anonymous bind may be disabled (normal for hardened IPA)"
38+ fi
3439
35- # ── PHASE 2: Health Checks ────────────────────────────────────────────────────
36- info " Phase 2: Health Checks"
40+ header " 3. FreeIPA Admin LDAP Bind"
41+ if ldapwhoami -x -H " ldap://$IPA_HOST :389" \
42+ -D " uid=admin,cn=users,cn=accounts,dc=lab,dc=local" \
43+ -w " $KC_PASS " 2> /dev/null | grep -q " dn:" ; then
44+ pass " Admin LDAP bind successful"
45+ else
46+ warn " Admin LDAP bind failed (FreeIPA may not be fully initialised)"
47+ fi
3748
38- if docker compose -f " ${COMPOSE_FILE} " ps | grep -q " running\|Up" ; then
39- pass " Container is running"
49+ header " 4. Users OU Exists in FreeIPA"
50+ USERS_OU=$( ldapsearch -x -H " ldap://$IPA_HOST :389" \
51+ -D " uid=admin,cn=users,cn=accounts,dc=lab,dc=local" -w " $KC_PASS " \
52+ -b " cn=users,cn=accounts,dc=lab,dc=local" " (objectClass=person)" uid 2> /dev/null | grep " uid:" | head -5)
53+ [[ -n " $USERS_OU " ]] && pass " FreeIPA users found in cn=users,cn=accounts" || fail " Users OU empty or unreachable"
54+
55+ header " 5. Keycloak Health"
56+ if curl -sf " $KC_URL /health/ready" | grep -q ' "status":"UP"' ; then
57+ pass " Keycloak /health/ready UP"
4058else
41- fail " Container is not running "
59+ fail " Keycloak not ready " ; exit 1
4260fi
4361
44- # ── PHASE 3: Functional Tests ─────────────────────────────────────────────────
45- info " Phase 3: Functional Tests (Lab 04 — SSO Integration)"
62+ header " 6. Keycloak Admin Auth + Realm Setup"
63+ TOKEN=$( kc_token)
64+ [[ -n " $TOKEN " ]] && pass " Admin token obtained" || { fail " Admin auth failed" ; exit 1; }
65+ curl -sf -X POST " $KC_URL /admin/realms" \
66+ -H " Authorization: Bearer $TOKEN " -H " Content-Type: application/json" \
67+ -d " {\" realm\" :\" $REALM \" ,\" enabled\" :true}" -o /dev/null \
68+ && pass " Realm '$REALM ' created/exists" || warn " Realm may already exist"
4669
47- # TODO: Add module-specific functional tests here
48- # Example:
49- # if curl -sf http://localhost:389/health > /dev/null 2>&1; then
50- # pass "Health endpoint responds"
51- # else
52- # fail "Health endpoint not reachable"
53- # fi
70+ header " 7. Create LDAP Federation Component (Keycloak → FreeIPA)"
71+ TOKEN=$( kc_token)
72+ LDAP_BODY=" {
73+ \" name\" : \" freeipa-ldap\" ,
74+ \" providerId\" : \" ldap\" ,
75+ \" providerType\" : \" org.keycloak.storage.UserStorageProvider\" ,
76+ \" config\" : {
77+ \" vendor\" : [\" rhds\" ],
78+ \" connectionUrl\" : [\" ldap://freeipa:389\" ],
79+ \" bindDn\" : [\" uid=admin,cn=users,cn=accounts,dc=lab,dc=local\" ],
80+ \" bindCredential\" : [\" $KC_PASS \" ],
81+ \" usersDn\" : [\" cn=users,cn=accounts,dc=lab,dc=local\" ],
82+ \" usernameLDAPAttribute\" : [\" uid\" ],
83+ \" rdnLDAPAttribute\" : [\" uid\" ],
84+ \" uuidLDAPAttribute\" : [\" ipaUniqueID\" ],
85+ \" userObjectClasses\" : [\" inetOrgPerson,organizationalPerson\" ],
86+ \" editMode\" : [\" READ_ONLY\" ],
87+ \" syncRegistrations\" : [\" false\" ],
88+ \" enabled\" : [\" true\" ]
89+ }
90+ }"
91+ STATUS=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $KC_URL /admin/realms/$REALM /components" \
92+ -H " Authorization: Bearer $TOKEN " -H " Content-Type: application/json" -d " $LDAP_BODY " )
93+ [[ " $STATUS " =~ ^(201| 409)$ ]] && pass " LDAP federation component created (HTTP $STATUS )" || fail " LDAP federation failed (HTTP $STATUS )"
5494
55- warn " Functional tests for Lab 01-04 pending implementation"
95+ header " 8. Trigger User Sync from FreeIPA"
96+ TOKEN=$( kc_token)
97+ COMP_ID=$( curl -sf " $KC_URL /admin/realms/$REALM /components?type=org.keycloak.storage.UserStorageProvider" \
98+ -H " Authorization: Bearer $TOKEN " | grep -o ' "id":"[^"]*"' | head -1 | cut -d' "' -f4)
99+ if [[ -n " $COMP_ID " ]]; then
100+ SYNC=$( curl -s -o /dev/null -w " %{http_code}" -X POST \
101+ " $KC_URL /admin/realms/$REALM /user-storage/$COMP_ID /sync?action=triggerFullSync" \
102+ -H " Authorization: Bearer $TOKEN " )
103+ [[ " $SYNC " =~ ^(200| 204)$ ]] && pass " Full sync triggered (HTTP $SYNC )" || warn " Sync returned HTTP $SYNC "
104+ else
105+ fail " LDAP federation component not found for sync"
106+ fi
56107
57- # ── PHASE 4: Cleanup ──────────────────────────────────────────────────────────
58- info " Phase 4: Cleanup"
59- docker compose -f " ${COMPOSE_FILE} " down -v --remove-orphans
60- info " Cleanup complete"
108+ header " 9. FreeIPA Users Visible in Keycloak"
109+ TOKEN=$( kc_token)
110+ sleep 5 # allow sync to complete
111+ USERS=$( curl -sf " $KC_URL /admin/realms/$REALM /users?max=50" -H " Authorization: Bearer $TOKEN " )
112+ ADMIN_USER=$( echo " $USERS " | grep -c ' "username":"admin"' || true)
113+ [[ " $ADMIN_USER " -gt 0 ]] && pass " FreeIPA admin user synced into Keycloak" || fail " FreeIPA users not synced"
114+ TOTAL=$( echo " $USERS " | grep -o ' "username"' | wc -l)
115+ [[ " $TOTAL " -gt 0 ]] && pass " Keycloak has $TOTAL users after LDAP sync" || fail " No users after sync"
61116
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} "
117+ header " 10. OIDC Discovery for it-stack Realm"
118+ DISC=$( curl -sf " $KC_URL /realms/$REALM /.well-known/openid-configuration" )
119+ echo " $DISC " | grep -q ' "token_endpoint"' && pass " OIDC discovery: token_endpoint present" || fail " OIDC discovery failed"
120+ echo " $DISC " | grep -q ' "authorization_endpoint"' && pass " OIDC discovery: authorization_endpoint present" || fail " Missing authorization_endpoint"
68121
69- if [ " ${FAIL} " -gt 0 ]; then
70- exit 1
71- fi
122+ header " 11. JWKS Endpoint"
123+ JWKS_URL=$( echo " $DISC " | grep -o ' "jwks_uri":"[^"]*"' | cut -d' "' -f4)
124+ curl -sf " $JWKS_URL " | grep -q ' "keys"' && pass " JWKS endpoint returns signing keys" || fail " JWKS endpoint failed"
125+
126+ echo
127+ echo " ═══════════════════════════════════════"
128+ echo " Lab 01-04 Results: $PASS passed, $FAIL failed"
129+ echo " ═══════════════════════════════════════"
130+ [[ " $FAIL " -eq 0 ]]
0 commit comments