Application Infrastructure Azure

Apache Guacamole with Keycloak SSO on Ubuntu 24.04 on Azure User Guide

| Product: Apache Guacamole with Keycloak SSO on Ubuntu 24.04 LTS on Azure

Overview

Apache Guacamole is a clientless remote desktop gateway: it delivers RDP, VNC and SSH sessions to a plain web browser, with no plugin and no client software on the user's machine. Keycloak is an open source identity and access management server that speaks OpenID Connect and SAML.

Pairing them is the standard way to put real single sign-on in front of remote access, and it is the pairing Guacamole's own documentation uses as its reference example. The work in that pairing is the OpenID Connect wiring: installing the SSO extension, telling Guacamole which identity provider to trust, registering a client in the identity provider with exactly the right redirect URI, and making the two agree on an absolute issuer URL. This image ships that wiring already done, and proven. On first boot the VM creates its own Keycloak realm, registers Guacamole as an OpenID Connect client bound to that VM's own address, creates your administrator, and then refuses to finish unless it can observe, on that specific VM, the whole round trip: an unauthenticated request handed to Keycloak, a wrong password rejected, the correct password accepted, and Guacamole issuing a session for the expected user.

What is included:

  • Apache Guacamole 1.6.0 (the guacamole web application, the guacd proxy daemon, and the JDBC and OpenID Connect extensions)
  • Keycloak 26.7.1 running as a production Quarkus build against PostgreSQL
  • Keycloak pre-wired as Guacamole's OpenID Connect identity provider, with single sign-on as the front door
  • PostgreSQL 16 holding both datastores on a dedicated Azure data disk
  • nginx terminating TLS on port 443 and serving both components from one address
  • A TLS private key generated on your VM at first boot, never baked into the image
  • Unique administrator credentials for both components, generated on the first boot of every VM
  • No default login of any kind: Guacamole's guacadmin account is removed from the database entirely

Who it is for: teams who need auditable, browser-based remote access to servers or desktops, and who want the accounts that grant that access to live in a real identity provider rather than in the remote access tool itself.

Prerequisites

  • An Azure subscription with permission to create virtual machines
  • A network security group allowing inbound TCP 443 (and 22 for administration) from your network
  • A VM size of at least Standard_B2ms (2 vCPU / 8 GiB). This appliance runs two JVMs and a database; smaller sizes will thrash.

Step 1 - Deploy from the Azure Marketplace

Search the Azure Marketplace for Apache Guacamole with Keycloak SSO on Ubuntu 24.04 LTS, select Create, and choose Standard_B2ms or larger. Allow inbound 443 from the networks your users will connect from, and 22 from your administration network only.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group my-resource-group \
  --name guacamole-sso \
  --image cloudimg:guacamole-keycloak:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

# then open the ports your users need
az vm open-port --resource-group my-resource-group --name guacamole-sso --port 443 --priority 1010

Step 3 - Connect to your VM

ssh azureuser@<public-ip>

Step 4 - Confirm the appliance is running

The appliance is five services: the database, the Guacamole proxy daemon, the Tomcat instance serving the Guacamole web application, Keycloak, and the nginx front end.

sudo systemctl is-active postgresql guacd tomcat9 keycloak nginx

All five report active.

Only nginx is reachable from outside the VM. Tomcat, Keycloak and guacd are bound to the loopback interface, so the only way in is through the TLS front door:

sudo ss -ltn | grep -E ':(443|80|8080|8081|4822) '

Five services active, with Tomcat, Keycloak and guacd bound to loopback only

Step 5 - Retrieve your credentials

Every credential on this VM was generated during its first boot and exists nowhere else. Read them once and store them in your password manager.

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

You get two identities:

  • SSO_ADMIN_USER / SSO_ADMIN_PASSWORD - the account you sign in to Guacamole with. It lives in Keycloak.
  • KEYCLOAK_ADMIN_USER / KEYCLOAK_ADMIN_PASSWORD - the Keycloak administration console, where you manage who is allowed to sign in.

The file is readable only by root:

sudo stat -c '%a %U:%G %n' /root/guacamole-keycloak-credentials.txt

The per-VM credentials file, mode 600 root:root, with both identities

Step 6 - Watch the single sign-on wiring working

This is the part that is normally hand-built. Ask Guacamole for a session without credentials and it does not offer you a password box - it hands you to this VM's own Keycloak realm:

PUB=$(sudo grep -m1 '^GUACAMOLE_URL=' /root/guacamole-keycloak-credentials.txt | sed -E 's|^GUACAMOLE_URL=https://([^/]+)/?.*|\1|')
curl -sk -m 20 --resolve "$PUB:443:127.0.0.1" \
     -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data '' \
     "https://$PUB/api/tokens" | python3 -m json.tool

The response carries a REDIRECT field pointing at /auth/realms/cloudimg/protocol/openid-connect/auth, with client_id=guacamole, response_type=id_token, and a single-use nonce that Guacamole itself generated and will insist on seeing again in the returned token.

An unauthenticated request handed to the Keycloak realm, carrying a Guacamole-issued nonce

Keycloak advertises itself at this VM's own absolute address, which is what makes the token Guacamole receives verifiable:

PUB=$(sudo grep -m1 '^GUACAMOLE_URL=' /root/guacamole-keycloak-credentials.txt | sed -E 's|^GUACAMOLE_URL=https://([^/]+)/?.*|\1|')
curl -sk -m 20 --resolve "$PUB:443:127.0.0.1" \
     "https://$PUB/auth/realms/cloudimg/.well-known/openid-configuration" \
  | python3 -c 'import sys,json; d=json.load(sys.stdin); print("issuer:", d["issuer"])'

Step 7 - Prove the whole round trip

The image ships the same end-to-end check that first boot ran. It drives the real browser flow: unauthenticated request, Keycloak login page, a deliberately wrong password, then the correct one, then Guacamole accepting the resulting token.

sudo /usr/local/sbin/guacamole-keycloak-sso-proof.sh

It prints SSO_PROOF_OK when every step passed.

There is no default login to fall back on. Guacamole normally ships a guacadmin account whose password is guacadmin; on this image that account is not disabled or renamed, it does not exist:

sudo -u postgres psql -tAc "SELECT count(*) FROM guacamole_entity WHERE name='guacadmin'" -d guacamole_db

That returns 0, and the login is refused:

curl -sk -m 20 -X POST --data-urlencode 'username=guacadmin' \
     --data-urlencode 'password=guacadmin' https://127.0.0.1/api/tokens

The round-trip proof passing, and the published default credentials refused

Step 8 - Sign in through the browser

Browse to https://<public-ip>/.

The TLS certificate was generated on your VM at first boot and is self-signed, so your browser shows a warning on the first visit. Step 12 covers replacing it.

You are taken straight to the Keycloak login page - this is the appliance's front door.

The Keycloak login page an unauthenticated visitor is handed to

Sign in with SSO_ADMIN_USER and SSO_ADMIN_PASSWORD from Step 5. Keycloak checks the password and returns you to Guacamole, which admits you as that user.

The Guacamole workspace after single sign-on completes

Step 9 - Add your first connection

In Guacamole, open the user menu (top right) and choose Settings, then Connections, then New Connection.

Give it a name, choose the protocol (RDP, VNC or SSH), and fill in the target host under Network:

  • RDP - hostname, port 3389, and a username and password for the target machine. For a Windows target, set Security mode to NLA and enable Ignore server certificate unless you have deployed a trusted certificate on it.
  • VNC - hostname, port 5901 (or whichever display you serve), and the VNC password.
  • SSH - hostname, port 22, and either a username and password or a private key.

Save, then click the connection on the Guacamole home screen. The session opens in the browser.

A live remote session rendering in the browser

Step 10 - Add more users

Users live in Keycloak, not in Guacamole. Sign in to the administration console at https://<public-ip>/auth/admin/ with KEYCLOAK_ADMIN_USER and KEYCLOAK_ADMIN_PASSWORD.

Switch the realm selector (top left) from master to cloudimg, open Users, then Add user. Give the account a username, first name, last name and email, then set a password on the Credentials tab.

Managing users in the cloudimg realm

Fill in first name, last name and email. Keycloak's default profile policy asks a user to complete missing profile fields at first sign-in, which interrupts the hand-off back to Guacamole.

A new user can sign in immediately, but sees no connections until you grant them some: in Guacamole, SettingsConnections → select the connection → Permissions, or grant through a group.

Because identity lives in Keycloak, everything Keycloak offers applies here without further configuration - two-factor authentication (AuthenticationRequired actionsConfigure OTP), password policy, account lockout, and federation to an existing LDAP or Active Directory directory (User federation).

Step 11 - Point the appliance at your own hostname

The image binds itself to the VM's public IP address on first boot, because OpenID Connect redirect URIs are absolute and must match exactly. To use a DNS name instead, three things must agree.

In the Keycloak administration console, realm cloudimgClientsguacamole, set Valid redirect URIs to https://<your-domain>/ and Web origins to https://<your-domain>.

Then, on the VM, update Guacamole's copy and restart it:

sudo sed -i 's|https://[^/]*/auth|https://<your-domain>/auth|g; s|^openid-redirect-uri:.*|openid-redirect-uri:           https://<your-domain>/|' /etc/guacamole/guacamole.properties
sudo sed -i 's|^KC_HOSTNAME=.*|KC_HOSTNAME=https://<your-domain>/auth|' /etc/keycloak/keycloak.env
sudo systemctl restart keycloak tomcat9

Step 12 - Replace the TLS certificate

The interim certificate is self-signed and unique to your VM. To install your own, replace the two files and reload nginx:

sudo install -m 600 /path/to/<your-domain>.key /etc/nginx/tls/server.key
sudo install -m 644 /path/to/<your-domain>.crt /etc/nginx/tls/server.crt
sudo nginx -t && sudo systemctl reload nginx

Network ports

Port Purpose
22 SSH administration. Restrict to your management network.
80 HTTP. Redirects to HTTPS; nothing is served over it.
443 The Guacamole workspace at / and the Keycloak identity server at /auth/.

Tomcat (8080), Keycloak (8081) and guacd (4822) listen on the loopback interface only and are not reachable from the network.

Where things live

Path Contents
/etc/guacamole/guacamole.properties Guacamole configuration, including the OpenID Connect settings
/etc/guacamole/extensions/ The JDBC and OpenID Connect extensions
/etc/keycloak/keycloak.env Keycloak runtime configuration
/opt/keycloak/ Keycloak installation
/opt/tomcat/ Tomcat 9 and the Guacamole web application
/var/lib/postgresql/ Dedicated Azure data disk holding both databases
/root/guacamole-keycloak-credentials.txt Per-VM credentials, mode 600

Troubleshooting

The browser warns about the certificate. Expected on first visit - the certificate is self-signed and generated on your VM. Step 12 replaces it.

Signing in loops back to the login page. The redirect URI, the Keycloak hostname and Guacamole's configuration must all name the same address. If you changed the hostname, re-check all three parts of Step 11. Confirm what Keycloak is advertising:

sudo grep -E '^openid-(issuer|redirect-uri)' /etc/guacamole/guacamole.properties

A user signs in but sees no connections. Keycloak proved who they are; Guacamole decides what they may open. Grant them permission on a connection (Step 10).

Keycloak did not start. It needs its database. Check both:

sudo systemctl is-active postgresql keycloak
sudo journalctl -u keycloak.service -n 40 --no-pager

Check first boot completed. The appliance writes a sentinel when its bootstrap finished:

ls -l /var/lib/cloudimg/guacamole-keycloak-firstboot.done
sudo tail -20 /var/log/cloudimg-firstboot.log

Remote sessions. guacd on Ubuntu 24.04 is version 1.3.0 and negotiates the 1.3 protocol with the 1.6 web application. RDP, VNC and SSH all work; parameters introduced after Guacamole 1.3 are not available. Note that guacd authenticates to SSH targets using RSA SHA-1 signatures, which OpenSSH 8.8 and later reject by default - use password authentication, or an ed25519 key, for SSH targets running a recent OpenSSH.

Support

This image is maintained by cloudimg. For support, contact support@cloudimg.co.uk.

Apache Guacamole is a trademark of The Apache Software Foundation. Keycloak is a trademark of the Linux Foundation. cloudimg is not affiliated with, endorsed by, or sponsored by either project or foundation; this image packages their freely available open source software, which remains licensed under the Apache License 2.0.