Apache Guacamole (RDP Access)
This guide explains how to open Remote Desktop Protocol (RDP) sessions from the browser using Apache Guacamole, utilizing VaultysHub for authentication. The user authenticates with their VaultysID, selects a server, and obtains the Windows desktop in a tab: they never see or enter the Windows account password.
How It Works
Browser ──HTTPS──▶ oauth2-proxy ──▶ Guacamole (web) ──▶ guacd ──RDP──▶ Windows Server
│ │
└── VaultysID ◀── VaultysHub (OpenID Connect)
- guacd opens the actual RDP session to the Windows server and relays it to the browser. Nothing is installed on the user's machine.
- Guacamole (web application) stores the connections: host, port, domain, account, and password. These credentials remain server-side and are never sent to the browser.
- VaultysHub authenticates the user. Guacamole receives their email address and grants access to the connections assigned to them.
- oauth2-proxy is recommended as a front-end (see below). It manages the OpenID Connect connection with VaultysHub and passes the user's email to Guacamole.
Why oauth2-proxy?
Guacamole's native OpenID module only handles the implicit flow. VaultysHub only allows it if the redirect URL is https:// and not localhost. oauth2-proxy uses the authorization code flow with PKCE: it works in all cases, and it is the recommended flow.
| Method | OIDC Flow | Guacamole in HTTP | Recommended |
|---|---|---|---|
| oauth2-proxy + header authentication | Authorization code + PKCE | Yes (testing only) | Yes |
| Guacamole's native OpenID module | Implicit | No, HTTPS required | If you cannot add oauth2-proxy |
Prerequisites
- Docker and Docker Compose on a machine that can reach the Windows servers on TCP port 3389.
- A DNS name and an HTTPS certificate for Guacamole in production (e.g.,
rdp.example.combehind your reverse proxy). - A Windows account per server or per environment, which Guacamole will use to open the session.
- Administrator access to VaultysHub.
Configuration in VaultysHub
1. Create the Application
- Log in to VaultysHub as an administrator.
- Go to Applications → Add and create the application:
- Name: Remote Desktop
- URL:
https://rdp.example.com/guacamole/
- In the application configuration, choose the type OIDC.
2. Set Up OpenID Connect
- Redirect URI:
https://rdp.example.com/oauth2/callback - Allowed Scope:
openid,email, andprofile - Subject Identifier (sub): Email address
- Use HTTP: disabled. This option is only for exposing VaultysHub's endpoints in
http://. - Require PKCE: can be enabled, oauth2-proxy sends a PKCE
S256.
Then note the Client ID (application identifier, noted as [appid] below) and the Client Secret.
Check the issuer announced by VaultysHub. This exact value must be provided to oauth2-proxy:
curl -s https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]/.well-known/openid-configuration | grep -o '"issuer":"[^"]*"'
3. Grant Access to Users
Assign the application to the relevant users or groups. A user who does not have access is denied by VaultysHub before reaching Guacamole.
Deploying Guacamole
1. Initialize the Database
mkdir -p guacamole/initdb guacamole/recordings && cd guacamole
docker run --rm guacamole/guacamole:1.6.0 /opt/guacamole/bin/initdb.sh --postgresql > initdb/initdb.sql
2. .env File
OIDC_CLIENT_ID=[appid]
OIDC_CLIENT_SECRET=your-client-secret
# Exact value of the "issuer" field noted in the previous step
OIDC_ISSUER=https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]
# 32 random characters: openssl rand -hex 16
COOKIE_SECRET=replace-me
POSTGRES_PASSWORD=replace-me
3. docker-compose.yml File
services:
guacd:
image: guacamole/guacd:1.6.0
restart: unless-stopped
volumes:
- ./recordings:/var/lib/guacamole/recordings
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: guacamole
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./initdb:/docker-entrypoint-initdb.d:ro
- ./pgdata:/var/lib/postgresql/data
guacamole:
image: guacamole/guacamole:1.6.0
restart: unless-stopped
depends_on: [guacd, postgres]
environment:
GUACD_HOSTNAME: guacd
POSTGRESQL_HOSTNAME: postgres
POSTGRESQL_DATABASE: guacamole_db
POSTGRESQL_USERNAME: guacamole
POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
# Creates the Guacamole user on their first login via VaultysHub
POSTGRESQL_AUTO_CREATE_ACCOUNTS: "true"
# User passed by oauth2-proxy
HTTP_AUTH_HEADER: X-Forwarded-Email
RECORDING_SEARCH_PATH: /var/lib/guacamole/recordings
volumes:
- ./recordings:/var/lib/guacamole/recordings:ro
# No "ports": Guacamole should only be reachable by oauth2-proxy
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.12.0
restart: unless-stopped
depends_on: [guacamole]
environment:
OAUTH2_PROXY_PROVIDER: oidc
OAUTH2_PROXY_OIDC_ISSUER_URL: ${OIDC_ISSUER}
OAUTH2_PROXY_CLIENT_ID: ${OIDC_CLIENT_ID}
OAUTH2_PROXY_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
OAUTH2_PROXY_REDIRECT_URL: https://rdp.example.com/oauth2/callback
OAUTH2_PROXY_SCOPE: openid email profile
OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256
OAUTH2_PROXY_EMAIL_DOMAINS: "*"
OAUTH2_PROXY_UPSTREAMS: http://guacamole:8080/
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
OAUTH2_PROXY_PASS_USER_HEADERS: "true"
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"
OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
OAUTH2_PROXY_COOKIE_SECURE: "true"
ports:
- "127.0.0.1:4180:4180"
Then publish 127.0.0.1:4180 behind your HTTPS reverse proxy at https://rdp.example.com, and start:
docker compose up -d
Guacamole trusts the X-Forwarded-Email header. Therefore, it should only be reachable by oauth2-proxy. Never expose its port 8080 directly: anyone could then send this header and impersonate another user.
4. First Administrator
The base comes with a local account guacadmin / guacadmin. With the configuration above, the login page is no longer accessible, so:
- Log in for the first time via VaultysHub at
https://rdp.example.com/guacamole/: your account is created, without rights. - Grant it administrative rights from the database:
docker compose exec -T postgres psql -U guacamole -d guacamole_db -c "
INSERT INTO guacamole_system_permission (entity_id, permission)
SELECT entity_id, p::guacamole_system_permission_type
FROM guacamole_entity, unnest(ARRAY['ADMINISTER','CREATE_CONNECTION','CREATE_CONNECTION_GROUP','CREATE_USER','CREATE_USER_GROUP']) p
WHERE name = '[email protected]' AND type = 'USER'
ON CONFLICT DO NOTHING;"
- Reload the page. The Settings menu appears. Then delete the
guacadminaccount (Settings → Users).
Configure an RDP Connection
In Settings → Connections → New Connection:
| Field | Value |
|---|---|
| Protocol | RDP |
| Hostname / Port | srv01.corp.local / 3389 |
| Username / Password | The Windows account used for this connection |
| Domain Name | The AD domain, e.g., CORP |
| Security Mode | NLA (Network Level Authentication) |
| Ignore Server Certificate | Only if the server uses a self-signed certificate |
Organize the servers by environment or domain with connection groups (New Group). Then, in Settings → Users (or Groups), check the connections each person is entitled to.
VaultysHub does not pass groups to the OIDC application. Therefore, rights are managed in Guacamole, by user or through Guacamole groups. VaultysHub decides who can enter, Guacamole decides which servers each can open.
Session Recording
In the Screen Recording section of the connection, fill in:
- Recording Path:
${HISTORY_PATH}/${HISTORY_UUID} - Automatically create a recording path: checked
Recordings are stored in ./recordings and can be replayed from Settings → History.
Testing the Configuration
- Open
https://rdp.example.com/guacamole/. - You are redirected to VaultysHub: validate with your VaultysID.
- You arrive in Guacamole with the list of your connections.
- Open a connection: the Windows desktop appears without entering a password.
- Open the application from the VaultysHub dashboard: the opening appears in the audit log. A direct connection to the Guacamole URL also goes through VaultysHub, but is not logged as an application opening.
Alternative: Guacamole's Native OpenID Module
If you cannot add oauth2-proxy, Guacamole can use VaultysHub directly. You must then serve Guacamole over HTTPS, on a name other than localhost.
In VaultysHub, declare the redirect URI https://rdp.example.com/guacamole/. Then, in the guacamole service, replace HTTP_AUTH_HEADER with:
OPENID_AUTHORIZATION_ENDPOINT: https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]/auth
OPENID_JWKS_ENDPOINT: https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]/jwks
OPENID_ISSUER: https://your-vaultyshub.link.vaultys.org/api/oidc/[appid]
OPENID_CLIENT_ID: "[appid]"
OPENID_REDIRECT_URI: https://rdp.example.com/guacamole/
OPENID_USERNAME_CLAIM_TYPE: email
OPENID_SCOPE: openid email profile
Then delete the oauth2-proxy service and publish Guacamole's port 8080 directly behind your HTTPS reverse proxy. The first administrator is created in the same way as in the First Administrator section.
Troubleshooting
oauth2-proxy Restarts in a Loop: "issuer did not match"
The value OIDC_ISSUER must be exactly the same as the issuer field from the OpenID configuration of VaultysHub (see the curl command above), including http or https. If the Use HTTP option is enabled on the application, the issuer starts with http://. Disable it unless VaultysHub itself is served over HTTP.
"Invalid redirect URI"
The URI declared in VaultysHub must match exactly what is sent: https://rdp.example.com/oauth2/callback with oauth2-proxy, https://rdp.example.com/guacamole/ with the native module (including the trailing slash).
The User Arrives in Guacamole Without Any Connections
This is normal on the first login: their Guacamole account has just been created. Assign them connections in Settings → Users.
The RDP Session Closes Immediately
- Check that guacd can reach the server:
docker compose exec guacd nc -zv srv01.corp.local 3389. - Try the security mode Automatic Negotiation and check Ignore Server Certificate to isolate a certificate issue.
- Check the logs:
docker compose logs guacd.
Security
- HTTPS required in production. Keep
OAUTH2_PROXY_COOKIE_SECURE: "true". - Guacamole never exposed directly when header authentication is active.
- A dedicated and limited Windows account per environment. Avoid a domain admin account usable everywhere.
- Session recording enabled for privileged accounts.
- Regular updates of guacd and Guacamole, which include FreeRDP.