Deployment guide

Apache Guacamole 1.6.0 Keycloak

Overview

Apache Guacamole is a clientless remote desktop gateway. Your users open a web page and get a full RDP, VNC or SSH session to a server behind it, with nothing installed on their own machine. Keycloak is an identity and access management server.

In this image, the two are wired together. Guacamole sends every sign-in to a Keycloak realm on the same instance over OpenID Connect. You manage users, password policy, brute-force lockout and multi-factor authentication in Keycloak, and you grant connections in Guacamole.

This guide covers launching and verifying Apache Guacamole 1.6.0 + Keycloak on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. The AMI is one version of the SecureAnyCloud Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace. You pick it as the software version when you subscribe and launch. The listing's guide hub describes the other VPN, proxy and remote access servers in the listing.

What you get:

  • Apache Guacamole 1.6.0: the web application on Apache Tomcat 9.0.123 and the guacd proxy daemon, built by SecureAnyCloud from the published 1.6.0 source, with RDP, VNC and SSH support.
  • Keycloak 26.8.1 in production mode, with a realm SecureAnyCloud and the OpenID Connect client guacamole already configured.
  • PostgreSQL 16, which holds Guacamole's connections and permissions and Keycloak's realm data, on 127.0.0.1 only.
  • nginx serving HTTPS on port 443 only, with a certificate generated for your instance on first boot.
  • The Guacamole extensions for PostgreSQL, OpenID Connect and brute-force protection (guacamole-auth-ban).
  • A self-test, guacamole-keycloak-selftest, and a helper to move the instance to a DNS name or Elastic IP, guacamole-keycloak-set-host.

Default user: ubuntu; Architecture: x86_64; Root volume: 16 GiB gp3

Security posture

  • No default credential. The image never creates Guacamole's published guacadmin account, and Keycloak has no administrator until your instance's first boot creates one. First boot generates the Keycloak administrator password, the single sign-on (SSO) user's password, both database passwords and the TLS key for this instance only. It writes the two sign-in passwords to /root/guacamole-keycloak-credentials.txt (root only).
  • No shared database. The image carries no PostgreSQL data at all. First boot creates a brand-new database cluster on your instance, so no password hash or identifier is shared with any other instance.
  • One public port. nginx listens on TCP 443. Tomcat (8080), Keycloak (8081), guacd (4822) and PostgreSQL (5432) listen on 127.0.0.1 only. The listing's security group opens SSH (22) and HTTPS (443).
  • Fixed host name. nginx answers only to the name this instance is configured for (its public IPv4 address, or the DNS name you set in Step 9). It closes the connection for any other Host header. Keycloak's issuer and every OpenID Connect redirect use that configured name, never a request header.
  • Real client addresses. nginx overwrites X-Forwarded-For with the address it sees. Tomcat and Keycloak trust forwarded headers from 127.0.0.1 only. Guacamole's logs and its brute-force ban therefore see the real client, and a forged header changes nothing.
  • Brute-force protection on both halves. guacamole-auth-ban bans an address for 5 minutes after 5 failed Guacamole logins, and Keycloak's brute-force detection is on for both realms. Self-registration is off.
  • Cross-site protection. The instance refuses with 403 a Guacamole WebSocket tunnel or a state-changing request whose Origin is not the instance's own address. Another web site therefore cannot open a session with your users' credentials.
  • No SSRF to instance credentials. guacd, Tomcat, Keycloak, PostgreSQL and nginx each carry an IPAddressDeny rule for the metadata addresses (169.254.169.254, fd00:ec2::254, 168.63.129.16), and the AMI is registered IMDSv2 only. guacd can still reach every other address, so your RDP, VNC and SSH targets work.
  • Patched components. Keycloak 26.8.1 and Tomcat 9.0.123 are the current releases. The Guacamole 1.6.0 web application bundles Jackson 2.19.0 and Logback 1.3.15, which have published security fixes. This image replaces exactly those libraries with Jackson 2.21.8 and Logback 1.3.16. guacd is built against Ubuntu's supported FreeRDP 3 libraries, and no build tools ship.
  • Fails closed. Every service starts only after first boot has written this instance's configuration and a ready marker. First boot finishes only after it has proved a full single sign-on round trip. If it cannot, every service stays stopped and first boot starts over on the next boot.
  • Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.

Prerequisites

You need:

  • An AWS account and permission to launch EC2 instances.
  • An EC2 key pair in the Region you launch in.
  • The public IP ranges your users connect from, and network access from the instance to the servers you want to reach (security groups, VPC routing or a VPN).

Recommended instance type: m5.large (2 vCPU, 8 GiB). Keycloak, Tomcat and PostgreSQL use about 1.5 GiB at rest. Add memory for many concurrent sessions.

Connecting to your instance

Version Operating system SSH user

Apache Guacamole 1.6.0 + Keycloak on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

1

Launch the instance

Option A: AWS Marketplace console

  • Open the SecureAnyCloud Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace. Choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  • When the subscription is active, choose Launch your software.
  • Choose One-click launch from AWS Marketplace, or Launch from EC2 Console for the full launch wizard. In Version, choose Apache Guacamole 1.6.0 + Keycloak on Ubuntu 24.04, then choose your Region.
  • Pick an instance type and your key pair. m5.large is recommended. The software charge is $0.04 per vCPU per hour.
  • Set the security group to allow SSH (22) from your own address and HTTPS (443) from the ranges your users connect from. Nothing else is needed.
  • Choose your VPC and a public subnet (or attach an Elastic IP later). Leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch.

Option B: AWS CLI

Run this command on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page. Replace the VPC, subnet, key pair and CIDR ranges with your own:

SG=$(aws ec2 create-security-group --group-name guacamole-keycloak \
  --description "Apache Guacamole + Keycloak" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr <your-client-cidr>
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=guacamole-keycloak}]'

You do not need --metadata-options, because the AMI already requires IMDSv2.

2

Connect and read the sign-in details

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot takes two to three minutes. It creates the database, starts Keycloak for the first time, creates the realm, client and users, and then proves that a sign-in works end to end. The details are in /root/guacamole-keycloak-credentials.txt, which only root can read. This command prints everything except the two password lines:

sudo grep -vE '^(SSO_ADMIN_PASSWORD|KEYCLOAK_ADMIN_PASSWORD)=' /root/guacamole-keycloak-credentials.txt
# Apache Guacamole 1.6.0 + Keycloak 26.8.1 - generated on first boot by guacamole-keycloak-firstboot.service.
# Every value below is unique to this instance; nothing was baked into the image. GUACAMOLE_URL=https://44.203.97.41/ KEYCLOAK_ADMIN_URL=https://44.203.97.41/auth/admin/
# Sign in to Guacamole with this identity (Guacamole hands you to Keycloak; Keycloak checks the password). SSO_ADMIN_USER=sac-admin
# The Keycloak administration console (master realm): users, login policy, MFA, identity providers. KEYCLOAK_ADMIN_USER=sac
# The certificate is self-signed and unique to this instance (browsers warn on the first visit). TLS_CERT_SHA256=68:77:20:0E:F3:3A:61:58:5C:AA:BA:C0:E8:A0:5E:22:2C:A3:3C:CA:60:AA:AE:20:F4:B5:A6:68:03:FB:CA:7F
# New DNS name or Elastic IP:
sudo guacamole-keycloak-set-host <name-or-ip>
# Self-test:
sudo guacamole-keycloak-selftest
# Guide: https://secureanycloud.com/stacks/apache-guacamole-1-6-0-keycloak/

There are two identities:

  • sac-admin (SSO_ADMIN_USER) signs in to Guacamole. It lives in the Keycloak realm SecureAnyCloud and is a Guacamole administrator.
  • SecureAnyCloud (KEYCLOAK_ADMIN_USER) signs in to the Keycloak administration console (master realm), where you manage users, policies and identity providers.

Print a password only when you need it, for example sudo sed -n 's/^SSO_ADMIN_PASSWORD=//p' /root/guacamole-keycloak-credentials.txt.

3

Confirm the stack is running

All five services and the first-boot unit are active. Only SSH and HTTPS listen on a public address:

systemctl is-active postgresql@16-main.service keycloak.service guacd.service tomcat.service nginx.service guacamole-keycloak-firstboot.service /usr/local/sbin/guacd -v cat /opt/keycloak/version.txt
sudo ss -Htln | awk '{print $4}' | sort -u
active active active active active active Guacamole proxy daemon (guacd) version 1.6.0 Keycloak - Version 26.8.1 0.0.0.0:22 0.0.0.0:443 127.0.0.1:4822 127.0.0.1:5432 127.0.0.53%lo:53 127.0.0.54:53 [::]:22 [::]:443 [::ffff:127.0.0.1]:8080 [::ffff:127.0.0.1]:8081

127.0.0.53 and 127.0.0.54 are Ubuntu's local DNS resolver.

4

Run the self-test

The self-test checks that every service is active and that guacd loads its RDP, VNC and SSH plugins. It then performs the whole single sign-on round trip through nginx exactly as a browser does. Guacamole sends the sign-in to Keycloak, a wrong password is refused, the SSO user's password is accepted, and Guacamole opens a session for sac-admin. The self-test also proves that Keycloak refuses admin/admin and that no guacadmin account exists. It prints no password:

sudo guacamole-keycloak-selftest
services: postgresql@16-main, keycloak, guacd, tomcat and nginx are active guacd plugins: rdp(88 params, VERSION_1_5_0) vnc(49 params, VERSION_1_5_0) ssh(42 params, VERSION_1_5_0) single sign-on: Guacamole -> Keycloak realm 'sac' -> per-instance password -> Guacamole session (wrong password refused) defaults: Keycloak admin/admin refused; the guacadmin account does not exist SELFTEST_OK
5

Sign in through the browser

Open GUACAMOLE_URL from Step 2 (for example https://44.203.97.41/). The certificate is self-signed, so the browser warns you on the first visit. Compare the fingerprint it shows with TLS_CERT_SHA256 and continue. Step 10 replaces the certificate with your own. Guacamole sends you straight to the Keycloak sign-in page of the realm.

Sign in as sac-admin with SSO_ADMIN_PASSWORD. Keycloak returns you to Guacamole, signed in. The home page lists your connections, which is empty until you add one.

6

Add your first connection

Open the user menu (top right), choose Settings, then Connections and New Connection. Give the connection a name and choose the protocol. Fill in the target's Hostname and Port under Parameters > Network (3389 for RDP, 5900 for VNC, 22 for SSH). If you want Guacamole to sign in for the user, also fill in the credentials under Authentication. Choose Save, and the connection appears on the home page.

This image's guacd supports RDP, VNC and SSH. The form also lists Telnet and Kubernetes, which this build does not include (Telnet is unencrypted). The target must be reachable from the instance. On the target, allow RDP, VNC or SSH from the Guacamole instance's security group, not from the internet.

Settings > Users shows every Guacamole identity. A user who signs in through Keycloak for the first time gets an account here automatically, with no connections until you grant them.

7

Add users in Keycloak

Users sign in with Keycloak, so you create them there. Open KEYCLOAK_ADMIN_URL (for example https://44.203.97.41/auth/admin/) and sign in as SecureAnyCloud with KEYCLOAK_ADMIN_PASSWORD. Switch to the Apache Guacamole realm (SecureAnyCloud) with Manage realms. Open Users, choose Add user, then set a password on the user's Credentials tab. The realm requires passwords of at least 12 characters that differ from the user name.

When the new user first signs in to Guacamole, Guacamole creates their account with no permissions. As sac-admin, open Settings > Users, select the user and tick the connections (or connection groups) they may use. To make someone a Guacamole administrator, grant Administer system on the same page.

Keycloak is also where you add multi-factor authentication. In the realm SecureAnyCloud, open Authentication and set OTP Form to Required in the browser flow, or give users the Configure OTP required action. You can also connect your corporate directory (User federation: LDAP or Active Directory) or another identity provider (Identity providers: SAML or OpenID Connect).

8

Brute-force protection

Guacamole loads the brute-force extension next to the PostgreSQL and OpenID Connect extensions. Its settings are in guacamole.properties: 5 failed logins from one address ban that address for 300 seconds. Keycloak's own brute-force detection is on for the master and SecureAnyCloud realms (Realm settings > Security defenses):

sudo ls /etc/guacamole/extensions
sudo grep -E '^(extension-priority|ban-)' /etc/guacamole/guacamole.properties
guacamole-auth-ban-1.6.0.jar guacamole-auth-jdbc-postgresql-1.6.0.jar guacamole-auth-sso-openid-1.6.0.jar extension-priority: openid ban-max-invalid-attempts: 5 ban-address-duration: 300

nginx passes the real client address and Tomcat trusts it from 127.0.0.1 only. The ban therefore applies to the attacker's address, not to everyone behind the proxy.

9

Use a DNS name or an Elastic IP

Sign-in redirects, the Keycloak issuer, the certificate and nginx all name one host. First boot uses the instance's public IPv4 address. If you stop and start the instance without an Elastic IP, the public address changes, and sign-in stops working until you run the helper again. The login message warns you when this happens. With no argument, guacamole-keycloak-set-host reads the current address from the instance metadata:

sudo guacamole-keycloak-set-host | tail -3
Host changed from 44.203.97.41 to 44.203.97.41: Guacamole https://44.203.97.41/ Keycloak admin https://44.203.97.41/auth/admin/ Certificate SHA-256: 35:97:DE:E8:01:55:42:BE:16:00:54:81:14:1A:D5:B1:85:2C:48:F8:46:36:FE:D1:55:1F:56:5C:E7:6B:00:4B SET_HOST_OK

To use your own name, point a DNS record at the instance and pass the name. Attach an Elastic IP first so the address never changes. For example: sudo guacamole-keycloak-set-host guac.example.com.

The helper updates the Keycloak client, Keycloak's hostname, Guacamole's OpenID Connect settings, nginx and the URLs in the credentials file. It issues a new self-signed certificate for that name, restarts the services and proves sign-in again. If nginx or Keycloak rejects the change, the helper restores the previous settings. It signs in to Keycloak with KEYCLOAK_ADMIN_USER and KEYCLOAK_ADMIN_PASSWORD from the credentials file, so if you change that password, update the file too.

10

Use your own certificate

After Step 9 sets the name, copy your certificate (full chain) and key over the self-signed pair and reload nginx:

sudo install -m 0644 fullchain.pem /etc/nginx/tls/server.crt
sudo install -m 0600 privkey.pem /etc/nginx/tls/server.key
sudo nginx -t && sudo systemctl reload nginx

Running guacamole-keycloak-set-host again later issues a new self-signed certificate, so copy your certificate back afterwards. Alternatively, put an Application Load Balancer with an AWS Certificate Manager certificate in front of port 443, and keep the self-signed certificate between the load balancer and the instance.

11

Metadata access is blocked

Every service of the stack carries an IPAddressDeny rule for the metadata addresses. A connection someone configures in Guacamole, a Keycloak identity-provider URL or anything else a user can influence therefore cannot reach the instance's credentials:

for u in postgresql@16-main keycloak guacd tomcat nginx; do printf '%-20s %s\n' "$u" "$(systemctl show -p IPAddressDeny --value "$u.service")"; done
postgresql@16-main 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32 keycloak 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32 guacd 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32 tomcat 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32 nginx 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
12

How the fail-closed start works

Every service is gated on /var/lib/sac/data/guacamole-keycloak.ready, which first boot writes only after this instance's database, configuration and certificate exist. Only root and the one service that needs them can read the files that hold secrets:

systemctl cat keycloak.service nginx.service | grep '^ConditionPathExists='
sudo stat -c '%a %U:%G %n' /root/guacamole-keycloak-credentials.txt /etc/guacamole/guacamole.properties /etc/keycloak/keycloak.env /etc/nginx/tls/server.key
ConditionPathExists=/var/lib/sac/data/guacamole-keycloak.ready ConditionPathExists=/var/lib/sac/data/guacamole-keycloak.ready 600 root:root /root/guacamole-keycloak-credentials.txt 640 root:tomcat /etc/guacamole/guacamole.properties 640 root:keycloak /etc/keycloak/keycloak.env 600 root:root /etc/nginx/tls/server.key

Network ports

Port Listens on Purpose

22/tcp all addresses SSH administration (restrict to your own address)

443/tcp all addresses Guacamole at / and Keycloak at /auth/ over HTTPS

8080/tcp 127.0.0.1 Tomcat (Guacamole web application), behind nginx

8081/tcp 127.0.0.1 Keycloak, behind nginx

4822/tcp 127.0.0.1 guacd

5432/tcp 127.0.0.1 PostgreSQL 16

Port 80 is not used. Give your users the https:// address.

Where things live

Path What it is

/root/guacamole-keycloak-credentials.txt Sign-in URLs and the two per-instance passwords

/etc/guacamole/guacamole.properties Guacamole settings (database, OpenID Connect, brute-force ban)

/etc/keycloak/keycloak.env Keycloak runtime settings (database, hostname, proxy)

/etc/nginx/sites-available/sac-guacamole-keycloak The HTTPS site; /etc/nginx/tls/ holds the certificate

/opt/tomcat/, /opt/keycloak/ Tomcat 9.0.123 with the Guacamole web application; Keycloak 26.8.1

/usr/local/sbin/guacd guacd 1.6.0

/var/lib/postgresql/16/main PostgreSQL data (created on first boot)

Logs are in the journal: journalctl -u tomcat (Guacamole sign-ins and connections), journalctl -u keycloak, journalctl -u guacd, and journalctl -u guacamole-keycloak-firstboot for first boot.

Troubleshooting

  • The browser shows "connection closed" or nothing at all. You used a name nginx is not configured for. Use GUACAMOLE_URL exactly, or run sudo guacamole-keycloak-set-host (Step 9).
  • "Invalid redirect URI" or a sign-in loop. The configured host no longer matches the instance's address, because the instance was stopped and started without an Elastic IP. Run sudo guacamole-keycloak-set-host.
  • "Too many failed authentication attempts." guacamole-auth-ban banned your address for 5 minutes after repeated failures. Wait, or restart Tomcat to clear the list.
  • A connection fails to start. Check that the target allows RDP, VNC or SSH from the Guacamole instance, and look at journalctl -u guacd.
  • First boot did not finish. journalctl -u guacamole-keycloak-firstboot shows the step that failed. Every service stays stopped until first boot succeeds, and a reboot retries it.

Security recommendations

  • Keep TCP 443 open only to the ranges your users connect from, and TCP 22 only to your management addresses.
  • Turn on multi-factor authentication in Keycloak for every user, especially administrators (Step 7).
  • Give each person their own Keycloak user, and grant only the connections they need in Guacamole.
  • Attach an Elastic IP and a DNS name, and use a certificate your users' browsers already trust (Steps 9 and 10).
  • Leave unattended security upgrades on, and reboot after kernel updates.

Support and licensing

Apache Guacamole and Apache Tomcat are projects of the Apache Software Foundation, and Keycloak is a Cloud Native Computing Foundation project. All three are distributed under the Apache License 2.0.

This image builds the unmodified Guacamole 1.6.0 server source. It ships the Guacamole 1.6.0 web application with its Jackson and Logback libraries updated as described above, and the unmodified Keycloak 26.8.1 and Tomcat 9.0.123 releases. SecureAnyCloud provides the packaging, the integration, the secure defaults, the per-instance credentials and certificate, the metadata block, the fail-closed start, the self-test and 24/7 support.

SecureAnyCloud is not affiliated with or endorsed by the Apache Software Foundation, the Keycloak project or the Cloud Native Computing Foundation. Apache, Apache Guacamole, Guacamole and Apache Tomcat are trademarks of the Apache Software Foundation. Keycloak is a trademark of its respective owner. They are used here only to identify the software.

For help with deployment or configuration, email sales@secureanycloud.com or visit SecureAnyCloud support.

Deploy on AWS

Launch Apache Guacamole 1.6.0 + Keycloak on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS