feat(docker): one-command Keycloak OIDC demo stack - #947
Conversation
Add docker-compose.oidc-demo.yml, which starts the published Studio image, Keycloak 26.4.7 with an imported realm, and a Caddy TLS proxy on one origin, https://demo.127.0.0.1.nip.io:8443, so SSO login and role mapping can be tried without an identity provider or a .env file. Document the demo in docs/OIDC.md before the provider setup, including the nip.io prerequisite, the one certificate warning and the Keycloak logout confirmation, and add one line to README.md. Closes libredb#941
yusuf-gundogdu
left a comment
There was a problem hiding this comment.
This does what the issue asked, and I checked it by running the stack rather than by reading the file.
One docker compose -f docker-compose.oidc-demo.yml up brought all three services healthy, with no .env, no hosts file edit and no second command. The browser warned once, on the one origin, and not again.
The values I observed, rather than a statement that they matched:
issuer https://demo.127.0.0.1.nip.io:8443/auth/realms/libredb
redirect_uri https://demo.127.0.0.1.nip.io:8443/api/auth/oidc/callback
post_logout https://demo.127.0.0.1.nip.io:8443/login (with client_id)
Role mapping is the part that actually had to work, and it does. Signing in as admin returned {"role":"admin"} from /api/auth/me and landed on /admin/overview. Signing in as user returned {"role":"user"} and the admin route redirected away. Logout cleared the Keycloak session: the next SSO click asked for credentials instead of signing me straight back in. docker compose down -v followed by up rebuilt the CA and reimported the realm with no leftover state.
Everything I flagged on the issue is handled, and the reasons are written into the file where the next person will find them rather than left implicit. The comment above reverse_proxy names all three places the port has to survive in X-Forwarded-Host. --proxy-headers=xforwarded is there with the 403 it prevents spelled out. The realm registers post.logout.redirect.uris as its own attribute. And the ca-export service exists precisely because Node ignores an unreadable NODE_EXTRA_CA_CERTS in silence, which is the failure that would have cost someone an afternoon.
Two things I suspected and was wrong about, recorded so nobody re-raises them. I expected the Keycloak health check to miss, since --http-relative-path=/auth also applies to the management port, but /auth/health/ready on 9000 is correct and the container reported healthy. And the em dash in the file header is house style here, not a slip: docker-compose.example.yml, which the issue named as the model to follow, carries fourteen of them.
src/ is untouched, both existing compose files are unchanged, and the walkthrough sits before the provider sections in docs/OIDC.md as the issue asked. Approving. Using Caddy's tls internal instead of hand-rolling certificates was your own call and it is the better one, since it removes a whole init script from the surface we have to maintain.
|
Thanks a lot @yusuf-gundogdu, for running the whole stack yourself and for the notes on #941 before I started. The port and the silent Good to know the health check path and the em dash are right as they are. @cevheri whenever you get a chance. The one open point from my side is still the Keycloak logout confirmation, which happens because no |
…dashboard
handleLogout POSTed /api/auth/logout and pushed /login without reading the
response, discarding the redirectUrl the route returns in OIDC mode. The local
cookie was cleared, so the app looked signed out while the session at the
identity provider stayed alive. src/hooks/use-auth.ts, the other caller of this
route, has always handled it correctly.
Invisible until now because src/lib/oidc.ts sends prompt=login on every
authorize request, so the provider asks for credentials whether or not the SSO
session survived. It falsified README.md and docs/OIDC.md, which both say
logout clears the identity provider session.
The branch had no coverage: the existing test mocked {success:true} only, so
the 100% line gate never saw it, because the missing code was not a line.
Found while reviewing libredb#947, whose walkthrough logs out from the admin dashboard.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Ran your stack rather than read it, and found a bug of ours sitting under your step 2. Your file was never the problem and is unchanged. The Logout button on #941 told you not to touch On your open point, the prompt is correct behaviour rather than a defect, and The stack reproduced exactly on Linux, unmodified, from one command. Merging, and thank you. |
|
Thanks @cevheri, and good catch on the admin header. That one was under my nose: step 1 lands on Understood on the logout point, and thanks for explaining why Thanks to you and @yusuf-gundogdu for running the stack yourselves. Good to know it reproduced unchanged on Linux. |
|
Following your point about
Happy to send these as one small PR. Given your gate, would you like to open an issue for it first, or is a PR straight onto the file I just added acceptable here? |
|
can you create an issue, after that I will assign it to you |
Description
A one-command Docker Compose stack that starts LibreDB Studio, a preconfigured Keycloak and a TLS proxy on a single origin, so SSO login and role mapping can be tried without reading the provider docs first. Follows the design in #941, with Caddy
tls internalas agreed on the issue.Type of Change
Related Issue
Closes #941
Changes Made
docker-compose.oidc-demo.yml(new, self-contained, with the Caddyfile and realm inline as composeconfigs):proxy:caddy:2.10-alpineon:8443,tls internal, network aliasdemo.127.0.0.1.nip.io./auth*goes to Keycloak and everything else to Studio. Its healthcheck waits forroot.crt.ca-export: one-shotinstall -m 0644of onlyroot.crtinto its own volume (details below).keycloak:quay.io/keycloak/keycloak:26.4.7,start-dev --import-realm --http-relative-path=/auth --proxy-headers=xforwarded --hostname=https://demo.127.0.0.1.nip.io:8443/auth, with a readiness healthcheck on the management port.libredb-studio:ghcr.io/libredb/libredb-studio:latest,NODE_EXTRA_CA_CERTS=/ca/root.crt, anddepends_onbothca-export: service_completed_successfullyandkeycloak: service_healthy. There is noJWT_SECRET, no.env, and no port published except the proxy's.libredb: confidential clientlibredb-studio, redirect URI, web origin andpost.logout.redirect.urisall on the single origin, realm rolesadmin/user, and usersadmin/adminanduser/user.docs/OIDC.md: new "Try it locally with Keycloak" section before Provider-Specific Setup, plus a TOC entry. It covers the prerequisites (Compose version, network, and the nip.io check with what a filtered resolver prints), the single certificate warning and the 12-hour lifetime, the three-step walkthrough, the Keycloak logout confirmation, and that this is not production.README.md: one line under Authentication & SSO.src/.docker-compose.ymlanddocker-compose.example.ymlare unchanged.Decisions worth a look
CA readability, and not sharing the key. Measured on a fresh Caddy volume: Caddy writes
/data/caddy/pki/authorities/local/root.crtasroot:root 0600inside0700directories, next toroot.key. Mounted directly, uid 1001 in Studio getsPermission denied, and as @yusuf-gundogdu found, Node then skipsNODE_EXTRA_CA_CERTSsilently. Mounting Caddy's data volume would also hand Studio the CA private key. Soca-exportcopies only the public certificate at0644into a separatedemo-cavolume, and Studio mounts only that one, read-only. Inside Studio:-rw-r--r-- root root /ca/root.crt.Startup ordering, observed on a cold
down -v/up(container start times, UTC):So Studio never starts before the certificate it trusts exists.
Roles in the ID token. The realm-roles mapper with Add to ID token is defined on the
libredb-studioclient (oidc-usermodel-realm-role-mapper, claimrealm_access.roles), not by editing the realm's built-inrolesclient scope. ImportingclientScopesin a realm file replaces all of Keycloak's default scopes, so overriding justroleswould also require redeclaringopenid,profile,emailand the rest. Happy to switch if you'd rather mirror the admin-console path indocs/OIDC.mdexactly.Logout shows one Keycloak confirmation.
buildLogoutUrlsendsclient_idandpost_logout_redirect_uribut noid_token_hint. With an active Keycloak session, Keycloak 26.4.7 answers that with a "Do you want to log out?" page (HTTP 200, button#kc-logout) instead of redirecting straight back. After one click it redirects to/loginand the Keycloak session is cleared: the next SSO click asks for credentials. That meets the acceptance criterion without touchingsrc/, and I documented it as step 2 of the walkthrough. Skipping the page would need the ID token kept in the session to sendid_token_hint, which is a product change, so I'm raising it here rather than doing it. Happy to open a separate issue if you want it.Testing
Run from an empty directory containing only
docker-compose.oidc-demo.yml(no checkout, no.env), on Docker 29.1.3 with Compose v2.40.3. The browser checks used headless Chrome (Playwright,channel: 'chrome').Cold start (
docker compose -f docker-compose.oidc-demo.yml up -d, returned after 17s):Keycloak:
Keycloak 26.4.7 on JVM (powered by Quarkus 3.27.1) started,Realm 'libredb' imported.Observed values
issuerhttps://demo.127.0.0.1.nip.io:8443/auth/realms/libredb(same asOIDC_ISSUER; openid-client accepted the ID token on both logins)authorization_endpointhttps://demo.127.0.0.1.nip.io:8443/auth/realms/libredb/protocol/openid-connect/authredirect_uriStudio sent to Keycloakhttps://demo.127.0.0.1.nip.io:8443/api/auth/oidc/callback(port preserved throughX-Forwarded-Host; matches the realm).../realms/libredb/protocol/openid-connect/logout?post_logout_redirect_uri=https%3A%2F%2Fdemo.127.0.0.1.nip.io%3A8443%2Flogin&client_id=libredb-studiogetent hosts demo.127.0.0.1.nip.iogives172.24.0.3(the proxy container, via the alias)NODE_EXTRA_CA_CERTS: issuer JSON returned. Unset:UNABLE_TO_GET_ISSUER_CERT_LOCALLYCaddy Local Authority - ECC Intermediate, SANDNS:demo.127.0.0.1.nip.io, valid 15:09:22 to 03:09:22 (12h)net::ERR_CERT_AUTHORITY_INVALIDonhttps://demo.127.0.0.1.nip.io:8443/login. During the full flow no other https origin is requested, so it warns once.The walkthrough, as run
Reproducibility:
docker compose down -vremoved both volumes and the network.up -da second time regenerated the Caddy CA, re-exported the certificate, re-imported the realm, and gave the same results for every row above.nip.io check, for the docs:
getent hosts demo.127.0.0.1.nip.ioprints127.0.0.1 demo.127.0.0.1.nip.io(exit 0). With a resolver that doesn't answer (--dns 127.0.0.1), it prints nothing and exits 2. macOS:dscacheutil -q host -a name demo.127.0.0.1.nip.io.Repo checks (on this branch):
bun run format,bun run readme:check,bun run security:check,bun run chart:checkwithCHART_SYNC_STRICT=1,channels:showcase:check, and 11 related test files (README, env documentation, packaging prune, distribution, chart version, LoginPage and others): 497 pass.helm-chart-agent.test.tsdidn't run locally because Helm isn't installed.Test Environment
ghcr.io/libredb/libredb-studio:latest(0.16.0)Checklist
src/changed)src/lib/db/providers/, I updated the matchingdocs/providers/documentation andtests/integration/db/tests in the same PR (provider triad): not applicableAdditional Notes
The translated READMEs didn't get the new line.
readme:checkonly compares engines and install commands, but I can add it to them if you'd like.