Skip to content

About

Docker identity lab: Keycloak, SAML, OIDC, LDAP and RADIUS, with break/fix scenarios, support runbooks and automated tests

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

identity-lab

Open in GitHub Codespaces CI

A Docker lab that reproduces real single sign-on and network login failures on demand: SAML, OIDC, LDAP, RADIUS, proxies and APIs. Each failure has a support runbook: the symptom, where to look, the fix and what to ask the customer. An automated test breaks every scenario, checks the user sees the right error, fixes it and proves the login works again.

When enterprise SSO breaks, support usually gets only "SSO stopped working for everyone". The cause is buried in signed XML, tokens, certificates, clocks and two teams' logs, and this lab makes it safe to practise finding it.

The SAML app's error page: Could not validate timestamp: expired. Check system clock. SP clock minus IssueInstant: +600 s. Runbook 02: clock skew

Symptom: the user signs in at the IdP, then the app refuses the assertion as expired.

Where to look: the app's clock against real UTC.
date -u; docker compose exec saml-sp date -u

Fix: correct the clock on the app's host. Don't widen the tolerance to hide it.
A broken login from a real scripts/test.sh run, beside the start of its runbook.

New here? Read the walkthrough's chapters 1 and 2: a real support ticket followed from the first message to the fix, in about 10 minutes.

How it fits together

flowchart LR
    browser(["Browser"])
    proxies["Reverse and egress proxies<br/>nginx, Squid"]
    apps["Apps and API<br/>SAML, OIDC, resource server"]
    kc["Keycloak<br/>identity provider"]
    ldap[("OpenLDAP<br/>directory")]
    radius["NAS and FreeRADIUS<br/>network login"]

    browser --> apps
    browser --> proxies
    browser -- "login form" --> kc
    proxies --> apps
    proxies --> kc
    apps -- "tokens, keys" --> kc
    kc -- "users, groups" --> ldap
    radius -- "users" --> ldap
Loading

Keycloak is the identity provider. It federates users from an OpenLDAP directory. SAML and OIDC apps and an API sit in front of it, some behind an nginx reverse proxy and one behind a Squid egress proxy. A NAS logs the same directory users in over RADIUS. The full architecture shows every container, network and channel.

60-second tour

One scenario from start to finish: the SAML app's clock runs 10 minutes fast.

  1. Break it: scripts/break.sh clock-skew. Only the app's container clock moves.
  2. See it: in a private window, open https://idlab.home:8181, click Log in with SAML and sign in as alice. The app refuses: Could not validate timestamp: expired. Check system clock.
  3. Diagnose it: runbook 02 says where to look. date -u; docker compose exec saml-sp date -u shows the two clocks 10 minutes apart.
  4. Fix it: scripts/fix.sh clock-skew, then log in again. It works.
  5. Prove it: scripts/test.sh does all of this for every scenario, with a real browser.

Quick start

Prerequisites: Linux with Docker Engine and Compose v2, plus bash, openssl, curl and jq. It uses host ports 8180 to 8189.

git clone https://github.com/MarkHawk/identity-lab && cd identity-lab

scripts/setup.sh                 # CA, certificates and random secrets -> .env and certs/ (prints no secrets)
                                 # set LAB_HOST=localhost first if you have no DNS name for this machine
docker compose up -d --wait      # the whole lab; the first build takes 10 minutes or more (Squid is compiled from source)
scripts/pin-idp-cert.sh          # "onboard" the SAML app: trust the IdP's current signing certificate
scripts/status.sh                # service health and the live state of every scenario
docker compose exec nas nas-auth dave   # a RADIUS login from the NAS; enter the demo password -> "nas: Access-Accept"

Then open the apps (HTTPS only, with a certificate warning to accept) and sign in as a demo user.

No Linux machine? Run it in GitHub Codespaces, step by step.

Scenarios

scripts/break.sh <scenario> breaks it, scripts/fix.sh <scenario> fixes it. The scenario reference has what each one breaks and its exact log lines.

# Scenario What the user sees Runbook
1 expired-signing-cert SAML app: IdP signing certificate expired on <date> 01-expired-signing-cert
2 clock-skew SAML app: Could not validate timestamp: expired. Check system clock. 02-clock-skew
3 redirect-uri-mismatch Keycloak: Invalid parameter: redirect_uri, before the login form 03-redirect-uri-mismatch
4 ldap-bind-failure Keycloak, directory users only: Unexpected error when handling authentication request to identity provider. 04-ldap-bind-failure
5 radius-shared-secret-mismatch NAS: RADIUS server not responding. Web sign-in still works. 05-radius-shared-secret-mismatch
6 proxy-bad-gateway nginx: 502 Bad Gateway. The app behind it is healthy. 06-proxy-bad-gateway
7 proxy-gateway-timeout nginx: 504 Gateway Time-out, 5 s after the password 07-proxy-gateway-timeout
8 proxy-rate-limit nginx: 429 Too Many Requests when the password is submitted 08-proxy-rate-limit
9 proxy-forwarded-headers Keycloak: Invalid Request. OIDC logins post to port 443. 09-proxy-forwarded-headers
10 proxy-header-too-large nginx: 502 Bad Gateway where the login page should be 10-proxy-header-too-large
11 proxy-auth-required App: Tunnel connection failed: 407 Proxy Authentication Required 11-proxy-auth-required
12 proxy-policy-block App: Tunnel connection failed: 403 Forbidden 12-proxy-policy-block
13 proxy-tls-interception App: certificate verify failed: self-signed certificate in certificate chain 13-proxy-tls-interception
14 proxy-bypass App: Failed to resolve 'keycloak'. The proxy logs nothing. 14-proxy-bypass
15 redirect-loop Browser: ERR_TOO_MANY_REDIRECTS, before any login 15-redirect-loop
16a api-bearer-audience API: 401, invalid_token: the audience doesn't include idlab-api. Login works. 16-api-bearer-401
16b api-bearer-expired API: 401, invalid_token: the token expired, 10 s after login 16-api-bearer-401
17 proxy-maintenance Proxy: Sign-in is down for planned maintenance (503). Health checks stay green. 17-service-unavailable
18a client-cert-missing Partner system: 400 Bad Request, No required SSL certificate was sent 18-client-cert
18b client-cert-expired Partner system: 400 Bad Request, The SSL certificate error 18-client-cert
19 cors-blocked Page: Failed to fetch; console: blocked by CORS policy. The API logs a 200. 19-cors-blocked

Go deeper

Related tools

How this was built

This project was built with AI-assisted tooling (Claude Code). I directed the scope, design and review. The assistant wrote much of the code and documentation, and every behaviour claimed here is checked by scripts/test.sh. Several design changes, such as the certificate pinning and the libfaketime monotonic-clock workaround, came from tests failing against the real software rather than from the first plan.

License

MIT

About

Docker identity lab: Keycloak, SAML, OIDC, LDAP and RADIUS, with break/fix scenarios, support runbooks and automated tests

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages