User Tools

Site Tools


computing:keycloak

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
computing:keycloak [2026/10/10 00:59] – created oemb1905computing:keycloak [2026/10/10 04:51] (current) – oemb1905
Line 1: Line 1:
-# Keycloak + Postgres — Rootless Podman Quadlet+-------------------------------------------
  
-Rootless Podman as user `worker` on host `support`. Apache + Let's Encrypt run as `root`. Keycloak is bound to localhost only.+  * **keycloak** 
 +  * **Jonathan Haack** 
 +  * **Haack's Networking** 
 +  * **webmaster@haacksnetworking.org**
  
-| Role | Bind | Public | +-------------------------------------------
-| --- | --- | --- | +
-| Keycloak | `127.0.0.1:8080` → container `8080` | `https://auth.haacksnetworking.org` | +
-| Postgres | internal only | none |+
  
-Images:+//Keycloak//
  
-- `docker.io/library/postgres:16-alpine` +------------------------------------------- 
-- `quay.io/keycloak/keycloak:26.7`+~~NOTOC~~
  
-Network: `keycloak_default`   +==== Introduction ====
-Data: `~/keycloak/postgres-data`   +
-Do not use `podman generate systemd`. Units come from Quadlet files in `~/.config/containers/systemd/`.+
  
----+This tutorial is for Debian users desiring to spin up an OCI container for Keycloak. In my case, Keycloak was a compatible auth endpoint for Incus' Web GUI. The web GUI for Incus is open by default, which is not wise for a production container/vm server! You either needed to build a custom cert for the reverse proxy and server to everyone and/or lock down with ''</Location>'' directives, or you had to tunnel via a proxy and access it locally. Both are cumbersome and/or risky and so I kept everything local and behind the ssh tunnel while I tinkered. To me, it was mandatory that Incus' Web GUI be fully public-facing, secure w/ TLS, and absolutely no janky browser barf -based solutions. For that reason, I first spun up podman, i.e., to spin up a Keycloak instance to attach to Incus' web GUI and liberate it from the tunnel. To begin with, the most minimal setup is the keycloack container and a separate postgres database for it to use:
  
-## 1. Directory (worker)+  * ''docker.io/library/postgres:16-alpine'' 
 +  * ''quay.io/keycloak/keycloak:26.7''
  
-```bash +Keycloak requires its own network in docker so I created a project directory, the network, and pulled the images.
-mkdir -p ~/keycloak/postgres-data +
-```+
  
---- 
  
-## 2. Network (worker) +==== 1. Directory (worker) ==== 
- +<code> 
-```bash+mkdir -p ~/keycloak/postgres-data 
 +podman pull docker.io/library/postgres:16-alpine 
 +podman pull quay.io/keycloak/keycloak:latest
 podman network exists keycloak_default || podman network create keycloak_default podman network exists keycloak_default || podman network create keycloak_default
-``` +</code>
- +
---- +
- +
-## 3. Secrets +
- +
-Placeholders used below: +
- +
-``` +
-POSTGRES_PASSWORD=replace-with-your-db-password +
-KC_BOOTSTRAP_ADMIN_PASSWORD=replace-with-your-bootstrap-admin-password +
-``` +
- +
-Bootstrap admin credentials apply only on an empty database. After the first start, change the admin password in the GUI. Do not rely on the env vars after that.+
  
----+==== 3. Secrets ====
  
-## 4. Quadlets+Now that we have the images and project directories, we can create the required secrets.
  
-File: `~/.config/containers/systemd/container-keycloak-postgres.container`+<code> 
 +umask 077 
 +openssl rand -hex 32 > ~/keycloak/db.secret 
 +openssl rand -hex 32 > ~/keycloak/bootstrap-admin.secret 
 +chmod 600 ~/keycloak/db.secret ~/keycloak/bootstrap-admin.secret 
 +</code>
  
-```bash +==== 4. Quadlets ====
-systemctl --user disable --now container-keycloak.service container-keycloak-postgres.service 2>/dev/null || true +
-rm -f ~/.config/systemd/user/container-keycloak.service \ +
-      ~/.config/systemd/user/container-keycloak-postgres.service +
-podman rm -f keycloak keycloak-postgres+
  
-mkdir -p ~/.config/containers/systemd+We can now safely create the quadlet systemd service units that run/monitor the keycloak and postgres containers. Tweak these snippets to your needs and or liking:
  
 +<code>
 cat > ~/.config/containers/systemd/container-keycloak-postgres.container << 'EOF' cat > ~/.config/containers/systemd/container-keycloak-postgres.container << 'EOF'
 [Container] [Container]
Line 107: Line 94:
 systemctl --user start container-keycloak-postgres.service systemctl --user start container-keycloak-postgres.service
 systemctl --user start container-keycloak.service systemctl --user start container-keycloak.service
-```+</code>
  
-`WantedBy=default.target` starts them. Do not `systemctl enable` a Quadlet unit. Do not `podman generate systemd`.+==== 5. Verify ====
  
-Keycloak depends on Postgres via the `[Unit]` After/Wants lines.+Once you create the quadlets, you want to start the service/container and make sure everything is in order. To do that, we just check the service and then run ''curl'' against the api endpoint.  
  
---- +<code>
- +
-## 5. Verify +
- +
-```bash+
 systemctl --user is-active container-keycloak-postgres.service container-keycloak.service systemctl --user is-active container-keycloak-postgres.service container-keycloak.service
 podman inspect keycloak-postgres keycloak --format '{{.Name}} cpus={{.HostConfig.NanoCpus}} memory={{.HostConfig.Memory}}' podman inspect keycloak-postgres keycloak --format '{{.Name}} cpus={{.HostConfig.NanoCpus}} memory={{.HostConfig.Memory}}'
 podman exec keycloak-postgres pg_isready -U keycloak podman exec keycloak-postgres pg_isready -U keycloak
 curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/health/ready || curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/ curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/health/ready || curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
-``` +</code>
- +
-Expect both `active`. Health may take a minute on first start while Keycloak builds. On 26.x the management health endpoint can be on port 9000 inside the container. If `curl http://127.0.0.1:8080/` returns the welcome page, the proxy path is fine.+
  
----+If everything is alright, we should see ''active''. If you don't, debug and clean up before proceeding. So long as everything is functional, we can pivot to setting up an upgrade script.
  
-## 6. Upgrade script+==== 6. Upgrade script ====
  
-`/usr/local/bin/upgrade-keycloak.sh`. A tag change is an edit to `Image=` in the matching `.container` file before `daemon-reload`. The script does not recreate the units.+OCI Containers aren't worth much without the ability to pull updates and patches quickly and painlessly. For me, a simple shell script gets this job done. Here's and example folks can tweak/edit to their liking:
  
-```bash+<code>
 #!/bin/bash #!/bin/bash
 set -euo pipefail set -euo pipefail
 +export XDG_RUNTIME_DIR=/run/user/$(id -u)
 +export DBUS_SESSION_BUS_ADDRESS=unix:path=${XDG_RUNTIME_DIR}/bus
  
 podman pull docker.io/library/postgres:16-alpine podman pull docker.io/library/postgres:16-alpine
Line 148: Line 131:
 systemctl --user start container-keycloak-postgres.service systemctl --user start container-keycloak-postgres.service
 systemctl --user start container-keycloak.service systemctl --user start container-keycloak.service
-```+</code>
  
-Run it as `worker`:+Once this runs successfully, you can then move on to setting up a reverse proxy with apache in order to forward url requests to the host/vm upstream to the keycloak service inside the container. 
  
-```bash +==== 7. Apache reverse proxy (root) ====
-su - worker -c '/bin/bash /usr/local/bin/upgrade-keycloak.sh' +
-```+
  
-Do not `sudo -u worker`. That drops the session bus.+Let's make sure the apache proxy/tunnel packages are enabled, as well as tls and location, and the certificate, are all active and ready to go. 
  
----+<code> 
 +a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl authz_host 
 +certbot certonly --apache -d auth.haacksnetworking.org 
 +</code>
  
-## 7. Apache reverse proxy (root)+Create the virtual host for http ''nano /etc/apache2/sites-available/auth.haacksnetworking.org.conf'':
  
-```bash +<code>
-a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl +
-``` +
- +
-`/etc/apache2/sites-available/auth.haacksnetworking.org.conf`: +
- +
-```apache+
 <VirtualHost *:80> <VirtualHost *:80>
     ServerName auth.haacksnetworking.org     ServerName auth.haacksnetworking.org
Line 174: Line 152:
     RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]     RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
 </VirtualHost> </VirtualHost>
 +</code>
  
 +Likewise, create the virtual host for the tls block. You can optionally drop this underneath the http vhost, but/and I prefer them to be separate.
 +
 +<code>
 <VirtualHost *:443> <VirtualHost *:443>
     ServerName auth.haacksnetworking.org     ServerName auth.haacksnetworking.org
Line 192: Line 174:
     CustomLog ${APACHE_LOG_DIR}/auth.haacksnetworking.org-access.log combined     CustomLog ${APACHE_LOG_DIR}/auth.haacksnetworking.org-access.log combined
 </VirtualHost> </VirtualHost>
-```+</code>
  
-```bash+Finally, check the configurations, restart the service, then check the endpoint. 
 + 
 +<code>
 a2ensite auth.haacksnetworking.org.conf a2ensite auth.haacksnetworking.org.conf
 apache2ctl configtest && systemctl reload apache2 apache2ctl configtest && systemctl reload apache2
 curl -sI https://auth.haacksnetworking.org/ curl -sI https://auth.haacksnetworking.org/
-```+</code>
  
-`KC_PROXY_HEADERS=xforwarded` matches the `X-Forwarded-*` headers. `KC_HOSTNAME` must be `https://auth.haacksnetworking.org`.+Check the output for any errors. In my notes, I had: ''KC_PROXY_HEADERS=xforwarded'' matches the ''X-Forwarded-*'' headers. ''KC_HOSTNAME'' must be ''https://auth.haacksnetworking.org''. I'm not sure these are 100% accurate output parameters, but/and they were in my notes. I can update later if I rebuild the instance. 
  
----+==== 8. First login ====
  
-## 8. First login+When you first open keycloak, you want to remove the temp password and set up the admin account and master realm.
  
-1. Open `https://auth.haacksnetworking.org` +  - Open ''https://auth.haacksnetworking.org'' 
-2. Administration Console +  - Administration Console 
-3. User: `admin`   +  - User: ''admin'' 
-   Password: the bootstrap value (first empty volume only) +  - Password: the bootstrap value (first empty volume only) 
-4. Admin → admin user → Credentials — set a new password, Temporary **off** +  - Admin → admin user → Credentials — set a new password, Temporary **off** 
-5. Realm settings → General +  - Realm settings → General 
-   - Frontend URL: `https://auth.haacksnetworking.org` +    * Frontend URL: ''https://auth.haacksnetworking.org'' 
-   - Require SSL: external requests (or all)+    * Require SSL: external requests (or all)
  
-Do not keep the bootstrap password.+==== 9. Incus OIDC ====
  
----+We will now create a dedicated real for Incus' Web GUI called "incus". Go to Realms > Add > incus > save. The endpoint should be ''https://auth.haacksnetworking.org/realms/incus''. Once you set it up, test with:
  
-## 9. Incus OIDC+  curl -sI https://auth.haacksnetworking.org/realms/incus/.well-known/openid-configuration
  
-Stay out of `master` for apps. Create a realm.+**Users** (in realm ''incus'')
  
-**Realm**+Create one user per person using these recommendations:
  
-- Name: `incus` +  * Username for SSO 
-- Enabled: on+  * Email filled in if ''oidc.claim=email'' 
 +  * Email verified: on 
 +  * Credentials → password, Temporary **off**
  
-Issuer (must match Incus exactly): +After you create the user account, you create the entry for what keycloak is being associated with:
- +
-`https://auth.haacksnetworking.org/realms/incus` +
- +
-```bash +
-curl -sI https://auth.haacksnetworking.org/realms/incus/.well-known/openid-configuration +
-``` +
- +
-Must be 200. The `issuer` value in that JSON is what Incus uses. +
- +
-**Users** (in realm `incus`) +
- +
-- Username for SSO +
-- Email filled in if `oidc.claim=email` +
-- Email verified: on +
-- Credentials → password, Temporary **off** +
- +
-Create one user per human. Do not log into Incus as Keycloak `admin` unless that user also exists in the `incus` realm.+
  
 **Client** **Client**
  
-- Clients → Create client +  * Clients → Create client 
-- Type: OpenID Connect +  * Type: OpenID Connect 
-- Client ID: `incus` +  * Client ID: ''incus'' 
-- Client authentication: **Off** (public / PKCE) +  * Client authentication: **Off** (public / PKCE) 
-- Standard flow: **On** +  * Standard flow: **On** 
-- Valid redirect URIs: `https://<incus-ui-host>/oidc/callback`   +  * Valid redirect URIs: ''https://<incus-ui-host>/oidc/callback'' 
-  Example: `https://support.haacksnetworking.org:8443/oidc/callback` +    Example: ''https://support.haacksnetworking.org:8443/oidc/callback'' 
-- Web origins: `https://<incus-ui-host>` +  * Web origins: ''https://<incus-ui-host>'' 
-- Save +  * Save
- +
-No client secret. Incus does not send one.+
  
 **Incus** **Incus**
  
-```bash+Configure incus on the CLI to accept the issuer: 
 + 
 +<code>
 incus config set oidc.issuer=https://auth.haacksnetworking.org/realms/incus/ incus config set oidc.issuer=https://auth.haacksnetworking.org/realms/incus/
 incus config set oidc.client.id=incus incus config set oidc.client.id=incus
 incus config set oidc.scopes=openid,email,profile incus config set oidc.scopes=openid,email,profile
 incus config set oidc.claim=preferred_username incus config set oidc.claim=preferred_username
-``` 
- 
-`oidc.claim` can be `email` instead. 
- 
-```bash 
 incus config show | grep oidc incus config show | grep oidc
-```+</code>
  
-Open the Incus UI → Login with SSO → Keycloak `incus` realm user.+Open the Incus UI → Login with SSO → Keycloak ''incus'' realm user. Then grant that identity. SSO alone is not admin:
  
-Then grant that identity. SSO alone is not admin: +<code>
- +
-```bash+
 incus auth identity list incus auth identity list
-``` +</code>
- +
-From the unix socket as root, grant Admin (or project access) to the new `oidc/` identity. Until you do, the UI logs in and then shows nothing / 403. +
- +
----+
  
-## Facts+From the unix socket as root, grant Admin (or project access) to the new ''oidc/'' identity. Until you do, the UI logs in and then shows nothing / 403. Alright, some of these are still in "raw note" form and I will be back to edit and update these notes if/when I have to rebuild or do further testing. While this instance is in production and working, some steps are unclear to me until I make it a third time.
  
-- + --- //[[alerts@haacksnetworking.org|oemb1905]] 2026/10/10 04:30//
computing/keycloak.1791593940.txt.gz · Last modified: by oemb1905