Sometimes it’s useful to get into your servers when you’re remote. There’s a lot of various ways to accomplish this. You can employ a VPN. You can use a remote access software like LogMeIn (hint: bad idea… don’t do this). You can also create an HTML5 web interface for accessing the machine from any browser. This is where Guacamole comes into the picture. It’s a pretty neat tool that allows you to safely expose SSH/RDP/VNC targets for remote access via SSL. In this post, I will walk through deploying Guacamole using Docker Compose on Ubuntu 22.04.
Prerequisites:
- Ubuntu 22.04 server with Docker and Docker Compose installed
Let’s create the working directory and the base docker-compose file:
mkdir /home/guacamole
cd /home/guacamole
nano docker-compose.yml
The basic docker compose is available on GitHub, but I’ve cleaned it up a bit and included it below:
networks:
guacnetwork_compose:
driver: bridge
services:
guacd:
container_name: guacd_compose
image: guacamole/guacd
networks:
- guacnetwork_compose
restart: always
volumes:
- ./drive:/drive:rw
- ./record:/record:rw
postgres:
container_name: postgres_guacamole_compose
environment:
PGDATA: /var/lib/postgresql/data/guacamole
POSTGRES_DB: guacamole_db
POSTGRES_PASSWORD: 'ChangeThisToSomethingSecret'
POSTGRES_USER: guacamole_user
image: postgres:15.2-alpine
networks:
- guacnetwork_compose
restart: always
volumes:
- ./init:/docker-entrypoint-initdb.d:z
- ./data:/var/lib/postgresql/data:Z
guacamole:
container_name: guacamole_compose
group_add:
- "1001"
depends_on:
- guacd
- postgres
environment:
GUACD_HOSTNAME: guacd
POSTGRES_DATABASE: guacamole_db
POSTGRES_HOSTNAME: postgres
POSTGRES_PASSWORD: 'ChangeThisToSomethingSecretMatchingAbove'
POSTGRES_USER: guacamole_user
RECORDING_SEARCH_PATH: /record
image: guacamole/guacamole
networks:
- guacnetwork_compose
volumes:
- ./record:/record:rw
ports:
- 8080/tcp
restart: always
nginx:
container_name: nginx_guacamole_compose
restart: always
image: nginx:latest
volumes:
- ./nginx/templates:/etc/nginx/templates:ro
- ./nginx/ssl/self.cert:/etc/nginx/ssl/self.cert:ro
- ./nginx/ssl/self-ssl.key:/etc/nginx/ssl/self-ssl.key:ro
ports:
- 8443:443
networks:
- guacnetwork_compose
** Note: The file in Github has an error in it referencing the wrong group ID. I corrected it to 1001 in the above.
The docker image is set to include the required extensions based on the environment variables you supply. For example, if you add TOTP_ENABLED: true
, the image build will include the TOTP extension.
So, with that being said, I STRONGLY recommend setting up some form of dual factor authentication. Guacamole supports TOTP and DUO. Even if you’re going to use an external authentication provider, such as SSO, LDAP, etc., the local admin account needs to be secured and TOTP or DUO would be the best choices to do that.
Configuring TOTP
In the docker-compose file, you will add the configuration under guacamole>environment. There are 5 configuration options that you should provide:
guacamole:
environment:
TOTP_ISSUER: 'Your Service or System'
TOTP_DIGITS: 8
TOTP_PERIOD: 30
TOTP_MODE: sha256
TOTP_ENABLED: true
Once you add these parameters to the compose, restart the pod with docker compose up -d
. Now, when users log in, they will get prompted to setup TOTP.
Configuring LDAP
In the docker-compose file, you will add the configuration under guacamole>environment. There are only 1 parameter that is required to be supplied:
guacamole:
environment:
LDAP-USER-BASE-DN: 'OU=folder,DC=my,DC=domain,DC=com'
There are a bunch of other parameters that are quite useful to provide. Here are some of them with comments explaining them:
guacamole:
environment:
## Your LDAP domain
LDAP_HOSTNAME: my.domain.com
## Your LDAP port. Usually 389 or 636
LDAP_PORT: 389
## Encryption Method, Supported values: none, ssl, starttls
LDAP_ENCRYPTION_METHOD: 'none'
## If you want to map LDAP groups to Guacamole groups, point to the parent folder where your groups are stored
LDAP_GROUP_BASE_DN: 'OU=Groups,DC=my,DC=domain,DC=com'
## You probably don't want every single user in LDAP user to have access, so use this to map to a specific group that governs access
LDAP_USER_SEARCH_FILTER: '(memberOf=CN=GroupName,OU=Groups,DC=my,DC=domain,DC=tech)'
## Define the LDAP attribute to map the username to. For Active Directory, this is usually sAMAccountName
LDAP_USERNAME_ATTRIBUTE: 'sAMAccountName'
## Assuming that you don't allow anonymous querying of LDAP, the following two parameters allow Guacamole to log into LDAP to query users
LDAP_SEARCH_BIND_DN: 'CN=username,OU=Users,DC=my,DC=domain,DC=com'
LDAP_SEARCH_BIND_PASSWORD: 'password'
Some LDAP tips:
- You can update your LDAP schema to map users to connections (servers you want to SSH/RDP into), but that’s complex and requires structural changes to your LDAP schema.
- Alternatively, you can create local users whose username match their LDAP username exactly and configure access on the local user.
- Security tip: If you check the box “Login disabled” on the local user account, it will force them to use their LDAP credentials to login.