Security Azure

Keycloak with OpenLDAP Directory on Ubuntu 24.04 on Azure User Guide

| Product: Keycloak with OpenLDAP Directory on Ubuntu 24.04 LTS on Azure

Overview

This image pairs the two halves of the canonical enterprise identity stack on one VM. OpenLDAP (slapd) is the authoritative user and group directory, and Keycloak is the modern identity provider in front of it, adding single sign on, OpenID Connect, OAuth 2.0 and SAML to accounts that live in LDAP. The pairing is not just co installed: on first boot the image wires a Keycloak LDAP user federation provider against the local directory in a dedicated realm named cloudimg, runs a full user sync, and proves that a directory stored user can obtain a token through Keycloak before the machine is handed to you.

What is included:

  • Keycloak 26.7.1 (Quarkus distribution) from the official GitHub release tarball at /opt/keycloak, OpenJDK 21
  • OpenLDAP 2.6 (slapd) from the Ubuntu 24.04 repository, mdb backend, base DN dc=example,dc=com
  • Keycloak realm cloudimg with an LDAP user federation provider (WRITABLE edit mode, registrations sync back to LDAP) pointing at ldap://127.0.0.1:389, users from ou=people,dc=example,dc=com
  • A seeded directory: ou=people, ou=groups and a demo user (uid=demo) that is synced into Keycloak and can sign in through it
  • LDAP on TCP 389 (with StartTLS), LDAPS on TCP 636 (per VM self signed certificate), Keycloak HTTP on TCP 8080
  • The directory database on a dedicated 20 GB data disk at /var/lib/ldap, independently resizable
  • Unique per VM passwords for the Keycloak admin (cloudimg), the directory admin (cn=admin,dc=example,dc=com) and the demo user, generated at first boot, no default logins
  • Verbatim licence texts in the image under /usr/share/cloudimg/licenses/ (Keycloak Apache 2.0, OpenLDAP Public License)
  • 24/7 cloudimg support

Prerequisites

Before deploying, ensure you have:

  1. An active Azure subscription
  2. An SSH key pair for the VM login user
  3. A virtual network and subnet (or let the portal create one)

Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) suits evaluation and small deployments. For production SSO with larger user populations, move Keycloak to a PostgreSQL backend and a D series size.

Network Ports

Protocol Port Description
TCP 22 SSH access
TCP 8080 Keycloak HTTP (admin console, OIDC/SAML endpoints)
TCP 389 LDAP (bind, search, StartTLS)
TCP 636 LDAPS (LDAP over TLS)

An LDAP endpoint reachable from the internet is a serious exposure: restrict 389 and 636 to your own VNet CIDR, and keep 8080 behind a TLS reverse proxy or restrict it to your management network for production.

Step 1: Deploy the Virtual Machine

Option A - Azure Portal: search Azure Marketplace for "Keycloak with OpenLDAP Directory cloudimg", select the offer, choose Standard_B2s, your SSH key and network, and create the VM. Open inbound ports 22 and 8080 (and 389/636 only to your VNet).

Option B - Azure CLI:

az vm create \
  --resource-group my-rg \
  --name keycloak-ldap-vm \
  --image cloudimg:keycloak-openldap:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_rsa.pub

Step 2: Connect via SSH

ssh azureuser@<vm-ip>

First boot takes a couple of minutes: the image rotates every credential, regenerates the LDAP TLS certificate, bootstraps the Keycloak admin, wires the LDAP federation and verifies a federated login before writing the credentials file.

Step 3: Read the Per VM Credentials

sudo cat /root/keycloak-openldap-credentials.txt

The file (mode 0600, root only) holds the three per VM secrets generated on first boot: KEYCLOAK_ADMIN_PASSWORD (console user cloudimg), OPENLDAP_ADMIN_PASSWORD (bind DN cn=admin,dc=example,dc=com) and DEMO_USER_PASSWORD (the LDAP demo user that signs in through Keycloak).

Step 4: Service Status and Versions

sudo systemctl status slapd --no-pager | head -5
sudo systemctl status keycloak --no-pager | head -5
sudo /opt/keycloak/bin/kc.sh --version 2>&1 | head -1
dpkg-query -W -f='slapd ${Version}\n' slapd

Expected output:

● slapd.service - LSB: OpenLDAP standalone server (Lightweight Directory Access Protocol)
     Active: active (running)
● keycloak.service - Keycloak 26 Identity & Access Management (cloudimg, OpenLDAP-federated)
     Active: active (running)
Keycloak 26.7.1
slapd 2.6.10+dfsg-0ubuntu0.24.04.1

slapd and keycloak services active, Keycloak 26.7.1 and slapd 2.6.10 versions, LDAP LDAPS and HTTP listeners bound

Step 5: Query the Directory

Bind as the directory administrator and list the seeded entries:

LDAP_PW=$(sudo grep -m1 '^OPENLDAP_ADMIN_PASSWORD=' /root/keycloak-openldap-credentials.txt | cut -d= -f2-)
ldapwhoami -x -H ldap://127.0.0.1 -D 'cn=admin,dc=example,dc=com' -w "$LDAP_PW"
ldapsearch -x -LLL -H ldap://127.0.0.1 -D 'cn=admin,dc=example,dc=com' -w "$LDAP_PW" \
  -b 'dc=example,dc=com' '(objectclass=*)' dn

Expected output:

dn:cn=admin,dc=example,dc=com
dn: dc=example,dc=com
dn: ou=people,dc=example,dc=com
dn: ou=groups,dc=example,dc=com
dn: uid=demo,ou=people,dc=example,dc=com

Authenticated ldapwhoami and ldapsearch listing the seeded directory tree

Step 6: Verify LDAPS

The image serves LDAP over TLS on port 636 with a certificate regenerated for this VM at first boot:

LDAP_PW=$(sudo grep -m1 '^OPENLDAP_ADMIN_PASSWORD=' /root/keycloak-openldap-credentials.txt | cut -d= -f2-)
LDAPTLS_REQCERT=never ldapwhoami -x -H ldaps://127.0.0.1:636 -D 'cn=admin,dc=example,dc=com' -w "$LDAP_PW"

Expected output:

dn:cn=admin,dc=example,dc=com

For production, replace the self signed pair at /etc/ldap/certs/ldap.crt and /etc/ldap/certs/ldap.key with a CA signed certificate and restart slapd.

Step 7: The Federation - LDAP Users Sign In Through Keycloak

Realm cloudimg federates its users from the local directory. Confirm the realm is live and prove the end to end path by obtaining an OpenID Connect token for the LDAP stored demo user:

curl -sf http://127.0.0.1:8080/realms/cloudimg/.well-known/openid-configuration | head -c 200; echo
DEMO_PW=$(sudo grep -m1 '^DEMO_USER_PASSWORD=' /root/keycloak-openldap-credentials.txt | cut -d= -f2-)
curl -sf -X POST http://127.0.0.1:8080/realms/cloudimg/protocol/openid-connect/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d "grant_type=password&client_id=admin-cli&username=demo&password=${DEMO_PW}" \
  | grep -o '"token_type":"Bearer"'

Expected output:

{"issuer":"http://127.0.0.1:8080/realms/cloudimg","authorization_endpoint":"http://127.0.0.1:8080/realms/cloudimg/protocol/openid-connect/auth", ...
"token_type":"Bearer"

The password you used is stored only in OpenLDAP - Keycloak validated it by binding to the directory. That is the bundle working as designed: the directory stays the single source of truth and Keycloak issues the tokens.

OIDC discovery for realm cloudimg and a successful password grant for the LDAP stored demo user

Step 8: Admin Console Login

Browse to http://<vm-ip>:8080/ - Keycloak 26 takes you straight to the admin console sign in form (there is no separate welcome page). Sign in as cloudimg with KEYCLOAK_ADMIN_PASSWORD from Step 3.

Keycloak admin console sign in form

After signing in you land on the master realm console. Keycloak flags the first boot admin as a temporary account - creating your own permanent administrator and deleting cloudimg is a recommended hardening step.

Keycloak master realm admin console after login

Step 9: The Pre Wired LDAP Federation Provider

Open Manage realms and switch to the cloudimg realm, then open User federation. The openldap provider is already configured and enabled against the local directory: connection ldap://127.0.0.1:389, users DN ou=people,dc=example,dc=com, bind DN cn=admin,dc=example,dc=com, WRITABLE edit mode with registration sync, so users created in Keycloak are written back to LDAP.

User federation page in realm cloudimg showing the pre configured openldap provider enabled

Step 10: Directory Users Inside Keycloak

Open Users in realm cloudimg. Because the realm has a federated provider the list starts empty - search for * (or a username) as the console suggests. The demo user is returned with its name and email attributes mapped from LDAP: it lives in OpenLDAP and was imported by the first boot full sync. Any user you add under ou=people appears here after a sync, and any user you create here is written into the directory.

Users page in realm cloudimg listing the LDAP sourced demo user with attributes mapped from the directory

Server Components

Component Version Path
Keycloak 26.7.1 /opt/keycloak/
OpenJDK 21 /usr/lib/jvm/
OpenLDAP slapd 2.6.10 /usr/sbin/slapd
ldap-utils 2.6.10 /usr/bin/ldapsearch etc.
Keycloak systemd unit - /etc/systemd/system/keycloak.service
Keycloak runtime env - /etc/keycloak/keycloak.env
Keycloak H2 database - /opt/keycloak/data/h2/
LDAP database (mdb) - /var/lib/ldap/ (dedicated data disk)
LDAP TLS certificate - /etc/ldap/certs/ldap.crt + ldap.key
First boot script - /usr/local/sbin/keycloak-openldap-firstboot.sh
Credentials - /root/keycloak-openldap-credentials.txt (0600 root:root)
Licences - /usr/share/cloudimg/licenses/

Filesystem Layout

Mount Point Size Description
/ 30 GB Root filesystem (Keycloak, OS, Keycloak H2 database)
/boot/efi 100 MB UEFI boot partition (Gen2 Hyper V)
/var/lib/ldap 20 GB Dedicated data disk for the OpenLDAP mdb database
/mnt varies Azure temporary resource disk

The directory database is separate from the OS disk: you can resize it independently and it is preserved on every VM created from this image.

Managing the Services

sudo systemctl restart slapd
sudo systemctl restart keycloak
sudo journalctl -u keycloak --no-pager | tail -20
sudo journalctl -u slapd --no-pager | tail -20

Keycloak takes 30 to 60 seconds to serve requests after a restart.

Production Hardening

  • Restrict the NSG: 389/636 to your VNet CIDR only; 8080 behind a TLS reverse proxy (or enable Keycloak HTTPS) and restrict to trusted networks
  • Keycloak database: move from H2 to PostgreSQL (KC_DB=postgres, KC_DB_URL=... in /etc/keycloak/keycloak.env, switch the unit from start-dev to start --hostname=<your-fqdn>) for production populations
  • LDAP TLS: install a CA signed certificate at /etc/ldap/certs/ and restart slapd; then set the federation provider's connection to ldaps://127.0.0.1:636 in the admin console for encrypted local binds
  • Directory ACLs: the default policy refuses anonymous writes and protects userPassword; review olcAccess before granting applications broader access
  • Create service accounts: bind applications with dedicated read only DNs under ou=people, never with cn=admin
  • Patching: unattended upgrades is enabled; still plan monthly apt-get update && apt-get upgrade && reboot

Troubleshooting

Keycloak is not reachable on 8080

sudo systemctl status keycloak --no-pager | head -5
sudo journalctl -u keycloak --no-pager | tail -30

If the unit reports a failed condition on first boot, the first boot service has not finished yet - check sudo journalctl -u keycloak-openldap-firstboot --no-pager | tail -30.

A directory user cannot sign in through Keycloak

sudo journalctl -u keycloak --no-pager | grep -i ldap | tail -10

Verify the user exists in LDAP (Step 5), then open User federation in realm cloudimg and use Sync all users. Keycloak validates passwords by binding to LDAP, so the user's userPassword must be set in the directory.

slapd will not start

sudo journalctl -u slapd --no-pager | tail -20
df -h /var/lib/ldap

Confirm the data disk is mounted at /var/lib/ldap (it is provisioned automatically from the image).

Licensing

Keycloak is Apache License 2.0; OpenLDAP is distributed under the OpenLDAP Public License (BSD style, OSI approved). Both verbatim licence texts ship in the image under /usr/share/cloudimg/licenses/. cloudimg provides commercial support separately.

Support