- openidconnect 3.5.0 -> 4.0.1 (oauth2 4 -> 5) - Eliminates duplicate reqwest/hyper/http dependency chains - 386 -> 355 crate dependencies - Resolves rustls-pemfile 1.0.4 unmaintained warning - HTTP client now uses stateful reqwest::Client (no-redirect policy) - exchange_code returns Result for EndpointMaybeSet token URLs - Remove JumpCloud from provider examples, prefer Authentik Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
Integrations
OIDC Single Sign-On
rustguac supports OpenID Connect for user authentication. Any OIDC provider works: Authentik, Keycloak, Okta, Azure AD, Google, etc.
Setup
- Register an application with your OIDC provider
- Set the redirect URI to
https://your-host/auth/callback - Note the client ID and client secret
- Add the
[oidc]section to your config:
[oidc]
issuer_url = "https://authentik.example.com/application/o/rustguac/"
client_id = "your-client-id"
client_secret = "your-client-secret"
redirect_uri = "https://your-host/auth/callback"
default_role = "operator"
groups_claim = "groups"
extra_scopes = ["groups"]
Client secret
The client_secret can be provided via the OIDC_CLIENT_SECRET environment variable, which takes precedence over the config file. This is recommended for production:
# For systemd
echo 'OIDC_CLIENT_SECRET=your-secret' >> /opt/rustguac/env
chmod 600 /opt/rustguac/env
OIDC groups
rustguac extracts group memberships from the OIDC ID token. The claim name is configurable (default: groups). Groups are used for:
- Automatic role assignment via group-to-role mappings (see Roles and Access Control)
- Address book folder access — folders can be restricted to specific OIDC groups
If your provider requires additional scopes to include groups in the token, add them to extra_scopes:
extra_scopes = ["groups"]
Login flow
- User clicks "Login" on the web UI
- Redirected to OIDC provider with PKCE challenge
- After authentication, provider redirects to
/auth/callback - rustguac validates the token (PKCE + nonce), extracts user info and groups
- User is created or updated in the database
- Group-to-role mappings are evaluated (highest matching role wins)
- A session cookie is set and the user is redirected to the application
Logout
GET /auth/logout clears the session cookie and deletes the auth session from the database.
Authentik setup guide
Authentik is a recommended open-source identity provider that works well with rustguac.
1. Create a provider in Authentik:
- Go to Applications > Providers > Create
- Select OAuth2/OpenID Connect
- Name:
rustguac - Authorization flow: pick your default authorization flow (e.g.,
default-provider-authorization-implicit-consent) - Client type: Confidential
- Redirect URIs:
https://your-rustguac-host/auth/callback - Under Advanced protocol settings:
- Scopes: ensure
openid,email,profileare selected - Add the
groupsscope (creates thegroupsclaim in the ID token)
- Scopes: ensure
2. Create an application:
- Go to Applications > Applications > Create
- Name:
rustguac - Slug:
rustguac - Provider: select the provider you just created
- Launch URL:
https://your-rustguac-host/
3. Note the provider details:
- Go back to the provider and note the Client ID and Client Secret
- The OpenID Configuration Issuer will be:
https://authentik.example.com/application/o/rustguac/
4. Configure rustguac:
[oidc]
issuer_url = "https://authentik.example.com/application/o/rustguac/"
client_id = "your-client-id"
redirect_uri = "https://your-rustguac-host/auth/callback"
default_role = "operator"
groups_claim = "groups"
extra_scopes = ["groups"]
echo 'OIDC_CLIENT_SECRET=your-client-secret' >> /opt/rustguac/env
chmod 600 /opt/rustguac/env
sudo systemctl restart rustguac
5. (Optional) Set up group-to-role mappings:
Create groups in Authentik (e.g., rustguac-admins, rustguac-operators) and assign users to them. Then configure group-to-role mappings in the rustguac Admin page so that group membership automatically assigns roles on login. See Roles and Access Control for details.
Vault / OpenBao Address Book
The address book stores connection entries in HashiCorp Vault or OpenBao KV v2. Credentials are read server-side and never sent to the browser.
Vault setup
1. Enable KV v2 (skip if already enabled):
vault secrets enable -path=secret kv-v2
2. Create a policy for rustguac:
vault policy write rustguac - <<'EOF'
path "secret/data/rustguac/*" {
capabilities = ["create", "read", "update", "delete"]
}
path "secret/metadata/rustguac/*" {
capabilities = ["list", "read", "delete"]
}
EOF
3. Enable AppRole auth and create a role:
vault auth enable approle
vault write auth/approle/role/rustguac \
token_policies="rustguac" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=0
# Get the role_id (put in config.toml)
vault read auth/approle/role/rustguac/role-id
# Generate a secret_id (set as VAULT_SECRET_ID env var)
vault write -f auth/approle/role/rustguac/secret-id
4. Configure rustguac:
[vault]
addr = "https://vault.example.com:8200"
role_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# mount = "secret" # KV v2 mount (default)
# base_path = "rustguac" # base path (default)
# namespace = "my-ns" # Vault Enterprise / OpenBao
# instance_name = "prod-1" # instance-scoped entries
Set the secret ID:
echo 'VAULT_SECRET_ID=<secret_id>' > /opt/rustguac/env
chmod 600 /opt/rustguac/env
KV v2 path structure
| Path | Description |
|---|---|
rustguac/shared/<folder>/.config |
Folder metadata: {"allowed_groups":[...], "description":"..."} |
rustguac/shared/<folder>/<entry> |
Connection entry (shared across all instances) |
rustguac/instance/<name>/<folder>/<entry> |
Instance-specific entry (requires instance_name) |
AppRole token management
rustguac manages Vault tokens automatically:
- Authenticates via AppRole (
role_id+secret_id) on startup - Renews tokens at 50% of their TTL
- Falls back to full re-authentication on 403 Forbidden responses
- Retries every 30 seconds if Vault is unavailable at startup (non-fatal)
Multi-instance support
When instance_name is set, rustguac sees both shared entries and entries scoped to its instance:
shared/entries are visible to all rustguac instancesinstance/<name>/entries are only visible to the named instance
This allows a fleet of rustguac instances to share common entries while maintaining instance-specific ones.
Entry types
Address book entries can be SSH, RDP, or Web connections. Each entry stores:
- Connection type and target (hostname, port, URL)
- Credentials (username, password, private key)
- Protocol-specific settings (domain, security mode, certificate ignore, drive override)
Name validation
Folder and entry names are validated: alphanumeric characters, hyphens, underscores, and dots only. Length 1-64. Characters like /, \, and .. are blocked to prevent path traversal in Vault.
Drive / File Transfer / LUKS Encryption
rustguac supports file transfer for RDP and SSH sessions.
RDP drive redirection
When drive is enabled, each RDP session gets a per-session directory under drive_path. guacd mounts this as a virtual drive visible in the remote Windows session (e.g., "Shared Drive" in Explorer).
- Files are temporary — the session directory is deleted when the session ends (configurable)
- The drive appears as a network drive in the Windows session
- Upload and download can be independently enabled/disabled
[drive]
enabled = true
drive_path = "/mnt/rustguac-drives"
drive_name = "Shared Drive"
allow_download = true
allow_upload = true
cleanup_on_close = true
retention_secs = 0
SSH SFTP
For SSH sessions, SFTP file transfer happens directly between the browser and the target SSH server via guacd. No files are stored on the rustguac server.
LUKS encryption
For RDP drive storage, the drive_path can be backed by a LUKS-encrypted volume. The encryption key is stored in Vault and the volume is only unlocked while rustguac is running.
[drive]
enabled = true
drive_path = "/mnt/rustguac-drives"
luks_device = "/opt/rustguac/drives.luks"
luks_name = "rustguac-drives"
luks_key_path = "rustguac/luks-key"
LUKS lifecycle
On startup:
- Read encryption key from Vault KV
- Open LUKS container via
sudo cryptsetup open --type luks --key-file=- - Mount the mapped device at
drive_path - Set ownership to the rustguac user
On shutdown:
- Unmount the volume
- Close the LUKS container via
sudo cryptsetup close
The key is passed to cryptsetup via stdin — never on the command line or written to disk.
Setup
Run the interactive setup script:
sudo /opt/rustguac/bin/drive-setup.sh
This creates the LUKS container file, generates a random encryption key, stores the key in Vault, and configures the necessary sudoers rules.
Sudoers rules
The rustguac user needs specific sudo permissions for LUKS operations. These are installed automatically:
rustguac ALL=(root) NOPASSWD: /usr/sbin/cryptsetup open --type luks --key-file=- <device> <name>
rustguac ALL=(root) NOPASSWD: /usr/sbin/cryptsetup close <name>
rustguac ALL=(root) NOPASSWD: /bin/mount /dev/mapper/<name> <mount_point>
rustguac ALL=(root) NOPASSWD: /bin/umount <mount_point>
rustguac ALL=(root) NOPASSWD: /bin/chown *:* <mount_point>
HAProxy Reverse Proxy
An example HAProxy configuration is provided in haproxy.example.cfg. This is the recommended production deployment pattern.
Features
- TLS termination at HAProxy with modern ciphersuites (TLS 1.2+, ECDHE)
- HTTP to HTTPS redirect
- X-Forwarded-For handling — strips incoming headers and adds the real client IP
- WebSocket support —
timeout tunnel 8hfor long-lived sessions - Health checks against
/api/health - Slowloris protection —
timeout http-request 10s - HSTS header —
max-age=31536000; includeSubDomains
Minimal example
frontend https
bind *:443 ssl crt /etc/ssl/private/rustguac.pem alpn h2,http/1.1
bind *:80
http-request redirect scheme https unless { ssl_fc }
http-request del-header X-Forwarded-For
option forwardfor
default_backend rustguac
backend rustguac
option httpchk GET /api/health
server rustguac 127.0.0.1:8089 ssl verify none check inter 30s
rustguac must trust HAProxy's IP:
trusted_proxies = ["127.0.0.1/32"]
Double TLS
In the default configuration, traffic is encrypted twice on the loopback:
- HAProxy terminates the client's TLS connection
- HAProxy connects to rustguac over TLS (rustguac's own self-signed cert)
This is belt-and-suspenders for environments where even loopback traffic should be encrypted.
Knocknoc Zero-Trust Access
Knocknoc provides identity-aware network access control. The integration works at the HAProxy layer: knocknoc-agent dynamically adds and removes client IPs to HAProxy ACLs via the admin socket.
How it works
- User authenticates through Knocknoc (SSO, MFA, etc.)
- knocknoc-agent adds the user's IP to HAProxy ACL #600 via the admin socket
- HAProxy allows access to the login page (
/path only) - User logs in via OIDC (rustguac's own auth layer)
- When the Knocknoc session expires, the IP is removed from the ACL
What is gated
Only the front page (/) is gated behind Knocknoc. All other paths pass through to rustguac's own authentication:
/api/*— API key or OIDC session auth/auth/*— OIDC login/callback flow/ws/*— WebSocket connections (session auth)/share/*— Share links (share token auth)
This ensures OIDC callbacks and share links work even when the user hasn't authenticated through Knocknoc, while hiding the login UI from scanners and bots.
HAProxy configuration
# Admin socket for knocknoc-agent
stats socket /run/haproxy/admin.sock mode 0660 level admin
# Dynamic ACL (ACL ID 600 must match Knocknoc config)
acl knoc_rustguac src -u 600
acl is_root path /
# Gate only the front page
use_backend rustguac if is_rustguac is_root knoc_rustguac
use_backend denied if is_rustguac is_root
use_backend rustguac if is_rustguac
Verifying ACL state
echo "show acl #600" | socat stdio /run/haproxy/admin.sock