Guacamole - Remote Access
Description
Apache Guacamole is a remote access gateway with a web frontend. It allows the user to connect to a device using SSH, VNC, or RDP using just a web browser.
Deployment details
Guacamole requires three separate Docker containers: (1) the backend server which handles the underlying connections, (2) the frontend server, which provides the web interface, and (3) a PostgreSQL database which stores the frontend's data.
These are the respective Docker container images:
Access
Guacamole is published behind the Secure Web Application Gateway at guac.kasad.com. It is protected by Cloudflare Zero Trust, requiring authentication to access.
Custom database container
Guacamole will not automatically initialize a database the first time it is run. Instead, this has to be done manually using an initialization script when creating the database container. To make this easier, I've created a Docker image specifically for Guacamole which will automatically extract the latest initialization script and create the database the first time it's run.
The sources are available on GitHub and the image is published to the GitHub Container Registry as ghcr.io/kdkasad/guacdb
.
Custom guacd
container
The upstream Guacamole project publishes nightly builds of the guacamole/guacd
Docker image.
However, the latest one (published 2022-09-06) broke support for Ed25519 SSH keys.
Until the next release occurs, I've built and published my own guacd
container using the latest sources from GitHub (commit 0361adc
).
This container uses the latest upstream sources and is published on Docker Hub as kiankasad/guacd:latest
.
Docker Compose stack
The Guacamole stack uses the following Docker Compose configuration:
version: '3'
services:
guacdb:
image: ghcr.io/kdkasad/guacdb:2022.09.06
container_name: guacdb
restart: unless-stopped
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: guac
POSTGRES_PASSWORD: [redacted]
volumes:
- ./guacdb-data:/var/lib/postgresql/data
guacd:
image: kiankasad/guacd:latest
container_name: guacd
restart: unless-stopped
guacamole:
image: guacamole/guacamole:latest
container_name: guacamole
restart: unless-stopped
environment:
REMOTE_IP_VALVE_ENABLED: true
GUACD_HOSTNAME: guacd
POSTGRES_HOSTNAME: guacdb
POSTGRES_DATABASE: guacamole_db
POSTGRES_USER: guac
POSTGRES_PASSWORD: [redacted]
POSTGRESQL_AUTO_CREATE_ACCOUNTS: true
OPENID_AUTHORIZATION_ENDPOINT: "https://auth2.kasad.com/application/o/authorize/"
OPENID_JWKS_ENDPOINT: "https://auth2.kasad.com/application/o/guacamole/jwks/"
OPENID_ISSUER: "https://auth2.kasad.com/"
OPENID_CLIENT_ID: "################################"
OPENID_REDIRECT_URI: "https://swag.kasad.com/guacamole/"
OPENID_USERNAME_CLAIM_TYPE: "preferred_username"
EXTENSION_PRIORITY: "openid"
depends_on:
- guacdb
- guacd
networks:
- default
- swag
networks:
default:
ipam:
driver: default
config:
- subnet: "172.18.0.0/16"
gateway: "172.18.0.1"
swag:
name: swag
external: true
Static network subnet
The reason for the specific network subnet is that one of the connections within Guacamole is to Kian's laptop. Since Kian's laptop is the host of the Docker containers, the easiest way to address it is as 172.18.0.1
. This requires that the subnet for the Guacamole stack is always at least 172.18.0.0/24
. I've set it to 172.18.0.0/16
because Docker usually assigns 16-bit subnets.
SWAG reverse proxy
The web frontend for Guacamole is reverse-proxied behind the Secure Web Application Gateway (SWAG). This means the swag
container needs network access to the guacamole
container, so the guacamole
container is added to the swag_default
network in the Compose stack.
Configuration
The Guacamole frontend is where the majority of the configuration happens, as it also handles authentication and storage of user preferences/data.
Guacamole is configured using a guacamole.properties
file. However, the Docker container allows for automatic generation of this configuration file using environment variables. So all of the configuration for Guacamole is done using environment variables in the Docker Compose file.
Single Sign-On
Guacamole's frontend server utilizes extensions to provide authentication backends. We use the openid
and postgresql
authentication extensions. OpenID Connect interfaces with Authentik to provide user authentication. The PostgreSQL backend stores the Guacamole-specific data for each user, like saved connections.
Because we're using two extensions, the order in which they are enabled matters. The guacamole/guacamole
Docker container automatically prioritizes the openid
extension, which is what we want. This way users must sign in through Authentik, and the PostgreSQL database is only used for data storage, not authentication.
The extension-priority
configuration option in the $GUACAMOLE_HOME/guacamole.properties
file can be used to override the extension loading order. The EXTENSION_PRIORITY
environment variable controls the same option when using the Docker container. However, this change is only in the upstream GitHub repository and hasn't made its way to the official Docker container yet. Despite this, I've defined it anyways (it doesn't hurt).
OpenID Connect parameters
We perform the necessary configuration using environment variables, which the Docker container will convert into configuration file entries.
The following environment variables are set for the guacamole
container. Obviously, replace the URLs and client ID to match your setup.
OPENID_AUTHORIZATION_ENDPOINT: "https://auth2.kasad.com/application/o/authorize/"
OPENID_JWKS_ENDPOINT: "https://auth2.kasad.com/application/o/guacamole/jwks/"
OPENID_ISSUER: "https://auth2.kasad.com/"
OPENID_CLIENT_ID: "[redacted]"
OPENID_REDIRECT_URI: "https://guac.kasad.com/" # Trailing slash is important
OPENID_USERNAME_CLAIM_TYPE: "preferred_username"
In Authentik, create a new Application and a new OpenID provider for Guacamole.
- Set the slug for the application to
guacamole
, as that's what we've used in the variables above. - Set the redirect URI to the URL of the frontend (including the trailing slash).
- Under Advanced protocol settings, set the Token validity to
minutes=300
. Guacamole will not accept any tokens with a lifetime greater than 300 minutes. - Also under Advanced protocol settings, set the Issuer mode to
Same identifier is used for all providers
, as that's what we've told Guacamole to expect.
The JWKS endpoint
If the JWKS endpoint is proxied behind Cloudflare (as ours is), it must have Cloudflare's Browser Integrity Check disabled. This can be accomplished by adding a Page Rule in the kasad.com zone for auth.kasad.com/jwks.json
.
If this is not done, Guacamole will be prohibited from accessing the JWKS endpoint. In the container's logs, you'll find error messages about 403 Prohibited errors when trying to access the JWKS URL.
Creating an admin user
When Guacamole's database is initialized, a user is created with the username and password set to guacadmin
.
This user has administrator permissions on the Guacamole instance, meaning they have full control over all aspects.
However, when logging in using SSO, this user is not accessible because the normal username/password login is not available.
To get around this, you have two options: (1) temporarily disable the SSO authentication extension, or (2) manually modify the Guacamole database.
I will not documennt option 2, but if you decide to go with that, see the System Permissions section of the Modifying data Manually documentation for Guacamole.
For option 1, you must first log in via SSO as the user you wish to turn into an administrator. This will create an entry for this user in the Guacamole database.
Next, disable the OpenID authentication extension by commenting out all the environment variable starting with OPENID_
in the Docker Compose file. Then re-create the stack using docker compose up -d
.
Now log in to Guacamole as the guacadmin
user. Then go to Settings > Users.
Select the user you created in the first step and make them an administrator.
While you're at it, change the password for the guacadmin
user just in case.
Finally, revert the changes to the Docker Compose file and re-create the stack again. Now your SSO user should be an administrator.
Adding new users
When a user signs in to Guacamole using SSO for the first time, an entry will be created in Guacamole's database, but they will not be given any permissions. This means they cannot create connections on their own.
An administrator must grant them the necessary permissions once their account has been created.
Other Notes
Building from sources
I tried building the Guacamole frontend and backend containers myself from their sources. The backend container built fine, but the frontend container failed because of a missing libglib-2.0.so.0
library.
Unstable mobile keyboard input on Android
I've noticed that the software keyboard input mode doesn't work well on Android (at least in Brave Browser). Keystrokes are not sent through the connection until the backspace key is pressed. A somewhat-workaround for this is to type what you wish to send, then add an extra space, then hit backspace. It should result in the proper text being sent.
Upstream development
Guacamole doesn't seem to be very popular. Unfortunately, this means that upstream development is pretty slow. Bugs (even relatively severe ones) are not fixed quickly.
SSH host keys
Guacamole doesn't accept the standard format for SSH host keys (i.e. the one you'd find in ~/.ssh/known_hosts
). However, it will not tell you that. If you attempt to use an SSH host key, it will simply inform you that "An internal error occurred."
guacamole/guacd
image versions
The Dockerfile for guacamole/guacd
is set up a certain way so that each build will pull the latest versions of the libraries it uses. A build bot automatically builds nightly versions of the guacamole/guacd
image and uploads them to Docker Hub.
However, the latest
tag still uses the latest tagged version of the guacd
source code. So although it builds nightly, it does not use the latest Git revision. Instead, it uses the latest release (currently 1.4.0
) and the newest libraries.
This means that the 1.4.0
and the latest
images tags are the same in terms of guacd
's functionality. The only way they differ is in what the underlying protocol libraries support.
VNC TLS failure
When connecting to a TigerVNC server using TLS transport security, a handshake error occurs. This only happens using the latest
tag for the guacamole/guacd
image. On tag 1.4.0
, it works fine.
Sadly, using 1.4.0
is not a workaround for this because 1.4.0
lacks support for OpenSSH-style SSH keys. So we must choose between VNC with TLS and ED25519 SSH keys. I choose the latter.
No Comments