Skip to content

Commit 015a7fa

Browse files
committed
feat(lab-05): iRedMail Advanced Integration -- OpenLDAP primary auth, Keycloak LDAP federation, mailhog relay
1 parent bf24fe9 commit 015a7fa

File tree

3 files changed

+270
-85
lines changed

3 files changed

+270
-85
lines changed

.github/workflows/ci.yml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,31 @@ jobs:
189189
run: docker compose -f docker/docker-compose.sso.yml logs
190190
- name: Cleanup
191191
if: always()
192-
run: docker compose -f docker/docker-compose.sso.yml down -v
192+
run: docker compose -f docker/docker-compose.sso.yml down -v
193+
lab-05-smoke:
194+
name: Lab 05 -- iRedMail Advanced Integration (LDAP + Keycloak federation)
195+
runs-on: ubuntu-latest
196+
needs: validate
197+
continue-on-error: true
198+
steps:
199+
- uses: actions/checkout@v4
200+
- name: Install tools
201+
run: sudo apt-get install -y curl ldap-utils
202+
- name: Validate integration compose
203+
run: docker compose -f docker/docker-compose.integration.yml config -q && echo "Integration compose valid"
204+
- name: Start integration stack
205+
run: docker compose -f docker/docker-compose.integration.yml up -d
206+
- name: Wait for Keycloak
207+
run: timeout 180 bash -c 'until curl -sf http://localhost:8108/health/ready | grep -q UP; do sleep 5; done'
208+
- name: Wait for OpenLDAP
209+
run: timeout 120 bash -c 'until docker exec iredmail-int-ldap ldapsearch -x -H ldap://localhost -b dc=lab,dc=local -D cn=admin,dc=lab,dc=local -w LdapAdmin05! > /dev/null 2>&1; do sleep 5; done'
210+
- name: Wait for iRedMail
211+
run: timeout 360 bash -c 'until curl -sf http://localhost:9180/ > /dev/null 2>&1; do sleep 15; done'
212+
- name: Run Lab 09-05 test script
213+
run: bash tests/labs/test-lab-09-05.sh --no-cleanup
214+
- name: Collect logs on failure
215+
if: failure()
216+
run: docker compose -f docker/docker-compose.integration.yml logs
217+
- name: Cleanup
218+
if: always()
219+
run: docker compose -f docker/docker-compose.integration.yml down -v
Lines changed: 115 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,121 @@
1-
# Lab 05 — Advanced Integration: iredmail with full IT-Stack ecosystem
2-
---
1+
# docker-compose.integration.yml -- Lab 05: Advanced Integration
2+
# iRedMail + OpenLDAP (FreeIPA sim) + Keycloak LDAP federation + Mailhog
3+
# Lab 05 tests: LDAP primary auth, Keycloak LDAP federation, mailbox from LDAP users
4+
#
5+
# Ports:
6+
# 9180 -- iRedMail HTTP admin (Roundcube / SOGo)
7+
# 9443 -- iRedMail HTTPS
8+
# 9025 -- SMTP (mailhog relay)
9+
# 9587 -- SMTP submission
10+
# 9143 -- IMAP
11+
# 9993 -- IMAPS
12+
# 8108 -- Keycloak admin console
13+
# 3892 -- OpenLDAP
14+
# 8025 -- Mailhog web UI
15+
#
16+
# Credentials:
17+
# LDAP admin: cn=admin,dc=lab,dc=local / LdapAdmin05!
18+
# LDAP readonly: cn=readonly,dc=lab,dc=local / ReadOnly05!
19+
# Keycloak: admin / Lab05Admin!
20+
# Mail domain: lab.local / postmaster@lab.local / MailAdmin05!
21+
322
services:
4-
iredmail:
5-
image: iredmail/iredmail:stable
6-
container_name: it-stack-iredmail
7-
restart: unless-stopped
23+
iredmail-int-ldap:
24+
image: osixia/openldap:1.5.0
25+
container_name: iredmail-int-ldap
26+
environment:
27+
LDAP_ORGANISATION: "IT-Stack Lab"
28+
LDAP_DOMAIN: lab.local
29+
LDAP_ADMIN_PASSWORD: LdapAdmin05!
30+
LDAP_READONLY_USER: "true"
31+
LDAP_READONLY_USER_USERNAME: readonly
32+
LDAP_READONLY_USER_PASSWORD: ReadOnly05!
833
ports:
9-
- "25:$firstPort"
34+
- "3892:389"
35+
networks:
36+
- mail-int-dir-net
37+
healthcheck:
38+
test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -b dc=lab,dc=local -D cn=admin,dc=lab,dc=local -w LdapAdmin05! > /dev/null 2>&1"]
39+
interval: 15s
40+
timeout: 10s
41+
retries: 5
42+
start_period: 20s
43+
44+
iredmail-int-keycloak:
45+
image: quay.io/keycloak/keycloak:24.0
46+
container_name: iredmail-int-keycloak
47+
command: start-dev
1048
environment:
11-
- IT_STACK_ENV=lab-05-integration
12-
- KEYCLOAK_URL=
13-
- DB_HOST=
14-
- REDIS_HOST=
15-
- SMTP_HOST=
16-
- GRAYLOG_HOST=
17-
extra_hosts:
18-
- "lab-id1:10.0.50.11"
19-
- "lab-db1:10.0.50.12"
20-
- "lab-proxy1:10.0.50.15"
49+
KC_HEALTH_ENABLED: "true"
50+
KEYCLOAK_ADMIN: admin
51+
KEYCLOAK_ADMIN_PASSWORD: Lab05Admin!
52+
ports:
53+
- "8108:8080"
2154
networks:
22-
- it-stack-net
55+
- mail-int-app-net
56+
- mail-int-dir-net
57+
healthcheck:
58+
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health/ready | grep -q UP || exit 1"]
59+
interval: 20s
60+
timeout: 10s
61+
retries: 10
62+
start_period: 60s
63+
64+
iredmail-int-smtp-relay:
65+
image: mailhog/mailhog:latest
66+
container_name: iredmail-int-smtp-relay
67+
ports:
68+
- "8025:8025"
69+
- "9025:1025"
70+
networks:
71+
- mail-int-app-net
72+
73+
iredmail-int-app:
74+
image: iredmail/mariadb:stable
75+
container_name: iredmail-int-app
76+
depends_on:
77+
iredmail-int-ldap:
78+
condition: service_healthy
79+
iredmail-int-keycloak:
80+
condition: service_healthy
81+
environment:
82+
HOSTNAME: mail.lab.local
83+
FIRST_MAIL_DOMAIN: lab.local
84+
FIRST_MAIL_DOMAIN_ADMIN_PASSWORD: MailAdmin05!
85+
MLMMJADMIN_API_TOKEN: MailApiToken05!
86+
ROUNDCUBE_DES_KEY: RoundcubeKey05xyz!
87+
LDAP_SERVER_HOST: iredmail-int-ldap
88+
LDAP_SERVER_PORT: "389"
89+
LDAP_BIND_DN: "cn=readonly,dc=lab,dc=local"
90+
LDAP_BIND_PASSWORD: ReadOnly05!
91+
LDAP_BASEDN: "dc=lab,dc=local"
92+
RELAY_HOST: iredmail-int-smtp-relay
93+
RELAY_PORT: "1025"
94+
ports:
95+
- "9180:80"
96+
- "9443:443"
97+
- "9587:587"
98+
- "9143:143"
99+
- "9993:993"
100+
volumes:
101+
- iredmail-int-data:/var/vmail
102+
- iredmail-int-db:/var/lib/mysql
103+
- iredmail-int-clamav:/var/lib/clamav
104+
networks:
105+
- mail-int-app-net
106+
- mail-int-dir-net
107+
healthcheck:
108+
test: ["CMD", "curl", "-f", "http://localhost/mail/"]
109+
interval: 30s
110+
timeout: 15s
111+
retries: 10
112+
start_period: 120s
113+
114+
volumes:
115+
iredmail-int-data:
116+
iredmail-int-db:
117+
iredmail-int-clamav:
23118

24119
networks:
25-
it-stack-net:
26-
driver: bridge
120+
mail-int-app-net:
121+
mail-int-dir-net:

tests/labs/test-lab-09-05.sh

Lines changed: 127 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,134 @@
11
#!/usr/bin/env bash
2-
# test-lab-09-05.sh — Lab 09-05: Advanced Integration
3-
# Module 09: iRedMail email server
4-
# iredmail integrated with full IT-Stack ecosystem
2+
# test-lab-09-05.sh -- Lab 05: iRedMail Advanced Integration
3+
# Tests: OpenLDAP bind, Keycloak realm+LDAP-federation, iRedMail LDAP config, mailhog relay
4+
#
5+
# Usage: bash tests/labs/test-lab-09-05.sh [--no-cleanup]
56
set -euo pipefail
67

7-
LAB_ID="09-05"
8-
LAB_NAME="Advanced Integration"
9-
MODULE="iredmail"
108
COMPOSE_FILE="docker/docker-compose.integration.yml"
11-
PASS=0
12-
FAIL=0
13-
14-
# ── Colors ────────────────────────────────────────────────────────────────────
15-
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
16-
CYAN='\033[0;36m'; NC='\033[0m'
17-
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"; }
22-
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 ""
28-
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
34-
35-
# ── PHASE 2: Health Checks ────────────────────────────────────────────────────
36-
info "Phase 2: Health Checks"
37-
38-
if docker compose -f "${COMPOSE_FILE}" ps | grep -q "running\|Up"; then
39-
pass "Container is running"
9+
KC_PORT=8108
10+
MAIL_PORT=9180
11+
LDAP_PORT=3892
12+
MAILHOG_PORT=8025
13+
KC_ADMIN=admin
14+
KC_PASS="Lab05Admin!"
15+
LDAP_ADMIN_DN="cn=admin,dc=lab,dc=local"
16+
LDAP_PASS="LdapAdmin05!"
17+
READONLY_DN="cn=readonly,dc=lab,dc=local"
18+
READONLY_PASS="ReadOnly05!"
19+
CLEANUP=true
20+
[[ "${1:-}" == "--no-cleanup" ]] && CLEANUP=false
21+
22+
PASS=0; FAIL=0
23+
pass() { echo "[PASS] $1"; ((PASS++)); }
24+
fail() { echo "[FAIL] $1"; ((FAIL++)); }
25+
section() { echo ""; echo "=== $1 ==="; }
26+
cleanup() { $CLEANUP && docker compose -f "$COMPOSE_FILE" down -v 2>/dev/null || true; }
27+
trap cleanup EXIT
28+
29+
section "Lab 09-05: iRedMail Advanced Integration"
30+
echo "Compose file: $COMPOSE_FILE"
31+
32+
section "1. Start Containers"
33+
docker compose -f "$COMPOSE_FILE" up -d
34+
echo "Waiting for services to initialize..."
35+
sleep 40
36+
37+
section "2. Keycloak Health"
38+
for i in $(seq 1 24); do
39+
if curl -sf "http://localhost:${KC_PORT}/health/ready" | grep -q "UP"; then
40+
pass "Keycloak health/ready UP"
41+
break
42+
fi
43+
[[ $i -eq 24 ]] && fail "Keycloak did not become healthy" && exit 1
44+
sleep 10
45+
done
46+
47+
section "3. OpenLDAP Connectivity"
48+
for i in $(seq 1 12); do
49+
if docker exec iredmail-int-ldap ldapsearch -x -H ldap://localhost \
50+
-b "dc=lab,dc=local" -D "$LDAP_ADMIN_DN" -w "$LDAP_PASS" \
51+
-s base "(objectClass=*)" >/dev/null 2>&1; then
52+
pass "LDAP admin bind successful"
53+
break
54+
fi
55+
[[ $i -eq 12 ]] && fail "LDAP admin bind failed after 120s"
56+
sleep 10
57+
done
58+
59+
# Readonly bind
60+
if docker exec iredmail-int-ldap ldapsearch -x -H ldap://localhost \
61+
-b "dc=lab,dc=local" -D "$READONLY_DN" -w "$READONLY_PASS" \
62+
-s base "(objectClass=*)" >/dev/null 2>&1; then
63+
pass "LDAP readonly bind successful"
4064
else
41-
fail "Container is not running"
65+
fail "LDAP readonly bind failed"
4266
fi
4367

44-
# ── PHASE 3: Functional Tests ─────────────────────────────────────────────────
45-
info "Phase 3: Functional Tests (Lab 05 — Advanced Integration)"
46-
47-
# TODO: Add module-specific functional tests here
48-
# Example:
49-
# if curl -sf http://localhost:25/health > /dev/null 2>&1; then
50-
# pass "Health endpoint responds"
51-
# else
52-
# fail "Health endpoint not reachable"
53-
# fi
54-
55-
warn "Functional tests for Lab 09-05 pending implementation"
56-
57-
# ── PHASE 4: Cleanup ──────────────────────────────────────────────────────────
58-
info "Phase 4: Cleanup"
59-
docker compose -f "${COMPOSE_FILE}" down -v --remove-orphans
60-
info "Cleanup complete"
61-
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}"
68-
69-
if [ "${FAIL}" -gt 0 ]; then
70-
exit 1
71-
fi
68+
section "4. Keycloak Realm + LDAP Federation"
69+
KC_TOKEN=$(curl -sf "http://localhost:${KC_PORT}/realms/master/protocol/openid-connect/token" \
70+
-d "client_id=admin-cli&grant_type=password&username=${KC_ADMIN}&password=${KC_PASS}" \
71+
| grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
72+
[[ -n "$KC_TOKEN" ]] && pass "Keycloak admin token obtained" || { fail "Keycloak admin token failed"; exit 1; }
73+
74+
HTTP=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
75+
"http://localhost:${KC_PORT}/admin/realms" \
76+
-H "Authorization: Bearer $KC_TOKEN" \
77+
-H "Content-Type: application/json" \
78+
-d '{"realm":"it-stack","enabled":true}')
79+
[[ "$HTTP" =~ ^(201|409)$ ]] && pass "Realm it-stack created (HTTP $HTTP)" || fail "Realm creation failed (HTTP $HTTP)"
80+
81+
LDAP_FED_PAYLOAD='{"name":"ldap","providerId":"ldap","providerType":"org.keycloak.storage.UserStorageProvider","config":{"vendor":["other"],"connectionUrl":["ldap://iredmail-int-ldap:389"],"bindDn":["'"$LDAP_ADMIN_DN"'"],"bindCredential":["LdapAdmin05!"],"usersDn":["dc=lab,dc=local"],"usernameLDAPAttribute":["uid"],"rdnLDAPAttribute":["uid"],"uuidLDAPAttribute":["entryUUID"],"userObjectClasses":["inetOrgPerson"],"syncRegistrations":["false"],"enabled":["true"]}}'
82+
HTTP=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
83+
"http://localhost:${KC_PORT}/admin/realms/it-stack/components" \
84+
-H "Authorization: Bearer $KC_TOKEN" \
85+
-H "Content-Type: application/json" \
86+
-d "$LDAP_FED_PAYLOAD")
87+
[[ "$HTTP" =~ ^(201|409)$ ]] && pass "Keycloak LDAP federation registered (HTTP $HTTP)" || fail "LDAP federation failed (HTTP $HTTP)"
88+
89+
section "5. iRedMail Environment"
90+
APP_ENV=$(docker inspect iredmail-int-app --format '{{range .Config.Env}}{{.}} {{end}}')
91+
92+
echo "$APP_ENV" | grep -q "LDAP_SERVER_HOST=iredmail-int-ldap" \
93+
&& pass "LDAP_SERVER_HOST=iredmail-int-ldap" \
94+
|| fail "LDAP_SERVER_HOST missing"
95+
96+
echo "$APP_ENV" | grep -q "LDAP_BIND_DN=cn=readonly" \
97+
&& pass "LDAP_BIND_DN uses readonly account" \
98+
|| fail "LDAP_BIND_DN missing/wrong"
99+
100+
echo "$APP_ENV" | grep -q "LDAP_BASEDN=dc=lab,dc=local" \
101+
&& pass "LDAP_BASEDN=dc=lab,dc=local" \
102+
|| fail "LDAP_BASEDN missing"
103+
104+
echo "$APP_ENV" | grep -q "RELAY_HOST=iredmail-int-smtp-relay" \
105+
&& pass "RELAY_HOST=iredmail-int-smtp-relay" \
106+
|| fail "RELAY_HOST missing"
107+
108+
section "6. iRedMail HTTP Admin Panel"
109+
for i in $(seq 1 20); do
110+
HTTP=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${MAIL_PORT}/" 2>/dev/null || echo "000")
111+
if [[ "$HTTP" =~ ^(200|302|301)$ ]]; then
112+
pass "iRedMail HTTP admin panel responds (HTTP $HTTP)"
113+
break
114+
fi
115+
[[ $i -eq 20 ]] && fail "iRedMail admin panel did not become ready (last HTTP $HTTP)"
116+
sleep 15
117+
done
118+
119+
section "7. Mailhog SMTP Relay"
120+
HTTP=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${MAILHOG_PORT}/" 2>/dev/null || echo "000")
121+
[[ "$HTTP" =~ ^(200|302)$ ]] \
122+
&& pass "Mailhog web UI accessible (HTTP $HTTP)" \
123+
|| fail "Mailhog web UI unreachable (HTTP $HTTP)"
124+
125+
section "8. Keycloak LDAP Components Listed"
126+
COMPONENTS=$(curl -sf "http://localhost:${KC_PORT}/admin/realms/it-stack/components?type=org.keycloak.storage.UserStorageProvider" \
127+
-H "Authorization: Bearer $KC_TOKEN" 2>/dev/null || echo "[]")
128+
echo "$COMPONENTS" | grep -q "ldap" \
129+
&& pass "Keycloak LDAP user storage provider listed" \
130+
|| fail "Keycloak LDAP provider not found in components"
131+
132+
section "Summary"
133+
echo "Passed: $PASS | Failed: $FAIL"
134+
[[ $FAIL -eq 0 ]] && echo "Lab 09-05 PASSED" || { echo "Lab 09-05 FAILED"; exit 1; }

0 commit comments

Comments
 (0)