Runbook β GIW POC Identity Platform
Status: POC Β· Date: 2026-09-21
1. Start, stop, reset
cd poc/identity
docker compose up -d --build # start (first build ~3-5 min)
docker compose ps # health of every service
docker compose logs -f keycloak # follow one service
docker compose down # stop, keep the database
docker compose down -v # stop and wipe β realm re-imports on next start
Rebuild one service after a code change:
docker compose up -d --build entitlement-service
Changing the Java authenticator or the login theme requires a Keycloak rebuild:
docker compose build keycloak && docker compose up -d keycloak
2. Verify
node tests/e2e.mjs # 39 tests
cd api && npx @redocly/cli lint giw-poc # OpenAPI
cd api/collection && npx @usebruno/cli run "01 HR Verification" --env local \
--env-var hrApiKey=giw-poc-hr-key-change-me
3. Trace one journey
Every request carries X-Correlation-ID. To follow one end to end:
CORR=corr-<uuid>
for c in giw-wifi-portal giw-keycloak giw-mock-hr-api giw-entitlement giw-session; do
echo "ββ $c"; docker logs $c 2>&1 | grep "$CORR"
done
The portal prints the correlation id on the Access Granted page, so a passenger can read it to support staff.
4. Inject failures
# HR times out
curl -X POST localhost:3001/admin/fault -H 'Content-Type: application/json' \
-d '{"mode":"timeout","delayMs":4000}'
# HR returns 503
curl -X POST localhost:3001/admin/fault -H 'Content-Type: application/json' \
-d '{"mode":"unavailable"}'
# back to normal
curl -X POST localhost:3001/admin/fault -H 'Content-Type: application/json' \
-d '{"mode":"none"}'
curl -s localhost:3001/health # shows the active fault mode
Stop a service outright to test a hard outage:
docker compose stop mock-hr-api # employee flow must fail closed
docker compose stop entitlement-service # both flows must fail closed
docker compose start mock-hr-api entitlement-service
5. Keycloak admin
http://localhost:8080 β admin / admin (from .env).
| Task | Where |
|---|---|
| See the employee flow | Authentication β Flows β employee-browser |
| Change the HR endpoint or timeout | Authentication β Flows β employee-browser β the giw-employee-verify execution β gear icon |
| Confirm the flow binding | Clients β wifi-portal-employee β Advanced β Authentication flow overrides β Browser Flow |
| Inspect a provisioned employee | Users β search emp- β Attributes |
| Watch events live | Realm settings β Sessions, and Events β User events |
| Check the claim mappers | Client scopes β giw-identity β Mappers |
6. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
dependency failed to start: container giw-keycloak is unhealthy |
Keycloak still booting, or the health probe is wrong | docker logs giw-keycloak. Health lives on port 9000, not 8080. |
Tokens have no profile/email claims |
A clientScopes array in a full realm import replaces Keycloak's built-ins |
Do not put clientScopes in realm-export.json. bootstrap creates giw-identity over the Admin API for exactly this reason. |
| Employee flow shows the ordinary password form | The flow binding override did not apply | docker logs giw-bootstrap β look for flow.binding.set. Re-run with docker compose up -d --force-recreate bootstrap. |
500 on the employee page |
FreeMarker error in employee-verify.ftl |
`docker logs giw-keycloak |
UNAUTHORIZED from Entitlement with a fresh token |
iss mismatch |
The token's iss is the browser-facing hostname (KC_HOSTNAME), while JWKS is fetched over the container network. OIDC_ISSUER and OIDC_JWKS_URI are separate variables on purpose. |
ENTITLEMENT_DENIED Β· COMPANY_NOT_ELIGIBLE |
Working as designed | The company is not in EMPLOYEE_COMPANIES. |
| Employee authenticates but gets BASIC | giw-identity scope not attached to the client |
Client scopes β wifi-portal-employee β Client scopes tab. |
RATE_LIMITED on a correct login |
5 failed guesses against that employee record inside 60 s | Wait, or curl -X POST localhost:3001/admin/reset-limits (test harness only). Successful logins are never counted. |
DMN_UNAVAILABLE |
Decision engine down β fails closed by design | docker compose ps dmn-service, docker logs giw-dmn. POLICY_FALLBACK=local enables the built-in table instead. |
| Rule change has no effect | The engine still holds the previous model | curl -X POST localhost:3005/api/v1/decisions/reload, then GET /api/v1/decisions to confirm the file it loaded. |
DMN_EVALUATION_ERROR |
The .dmn loaded but cannot evaluate |
A modelling bug, not a denial. docker logs giw-dmn shows the engine messages. |
IDENTITY_LINK_CONFLICT |
Two users share one employee_ref |
Manual data fix. The flow refuses to guess. |
| Portal session lost after a restart | In-memory store | Expected. Log in again. |
| Ports already in use | 3000-3003, 5432, 8080, 9000 | lsof -ti tcp:8080 |
| Maven build is slow or fails | First build resolves the Keycloak SPI tree | Needs network access to repo1.maven.org. The layer caches afterwards. |
7. What breaks when
| Service down | Customer flow | Employee flow |
|---|---|---|
dmn-service |
fails closed β no rules, no access | fails closed |
mock-hr-api |
unaffected | fails closed at verification |
keycloak |
fails | fails |
entitlement-service |
authenticates, then 502 β no access |
same |
session-service |
entitlement issued, then 502 β no access |
same |
postgres |
Keycloak fails | Keycloak fails |
No path grants access when a dependency is down. That is the property worth re-testing after any change.
8. Health
for p in 3000 3001 3002 3003; do echo -n "$p: "; curl -s localhost:$p/health; echo; done
curl -s localhost:9000/health/ready # Keycloak
9. Before this leaves a laptop
Standing instruction: it does not leave the laptop. No cloud, no test environment, no shared server, no production, no external hosting. The list below is what a future deployment would have to satisfy β it is not a to-do list for now. See ENVIRONMENTS.md Β§3.
- Replace every secret in
.env -
DEBUG_EXPOSE_TOKEN=0 - Remove
POST /admin/faultandPOST /admin/reset-limitsfrommock-hr-apiandmock-loyalty-api - Remove the service landing pages (
GET /on ports 3001β3003, 3005β3006) β they list the endpoint surface - Authenticate
POST /api/v1/decisions/reload, and put a rule change behind an approval workflow - TLS:
sslRequired: external, httpsKC_HOSTNAME,Secureon thesidcookie - Replace both
X-API-Keylegs with mTLS or service tokens - Replace the in-memory entitlement and session stores
- Add rate limiting to Entitlement and Session
- Narrow the accepted audience β stop accepting
account - Replace Direct Grant with browser OIDC / federation β Mode B in
AUTH-STRATEGY.md. Direct Grant is POC only. - Close decision D7 (Controller/Processor) before pointing at real HR data
- Capture a consent artefact for the CCCD path, or remove CCCD per ADR-003