Ad-hoc sessions had their own form on the Sessions page with a subset
of the options a connection entry has. "Connect to a host" now opens
the Connections entry editor in an ad-hoc mode instead, so an ad-hoc
connection has every option an entry has, now and as entries gain more.
The button sits beside the global search box on every page that has
one. It is shown to poweruser and above under the same rules as the
search box, and opens the editor in place on Connections or goes to
/connections.html#adhoc elsewhere. Ad-hoc mode has no name or folder,
Connect instead of Save, and for admins a "Save as entry" action.
POST /api/addressbook/adhoc/connect takes the entry as the editor
builds it. It resolves only the caller's own credential variables,
never lends folder defaults, and goes through apply_adhoc_policy and
the session rate limit like every other ad-hoc route.
POST /api/sessions is unchanged.
session_request_from_entry() replaces the two copies of the entry to
session request conversion, used by the Connections connect endpoint
and /api/connect, so an option added to entries works in all three.
can_connect_adhoc() is the one place that decides who may connect
ad-hoc.
The Sessions ad-hoc form is removed. loadFolders() now runs its
callback, so #open= links work again.
Refs sol1/rustguac#295
(cherry picked from commit dd40bf08ec)
Add the page to the Documentation lists in the README and overview, and
link it where SPICE and Proxmox come up: the session types in the
overview, the session and entry fields in the API reference, the
vnc_allowed_networks row in the configuration reference, and the entry
types and jump-host table in integrations. Those two lists also now name
SPICE and Proxmox VE, which they had left out, and no longer claim that
every session type can use a jump host, since VDI cannot.
(cherry picked from commit 638c579402)
A few examples used real-world names instead of reserved ones:
- the web session example pointed at www.saucedemo.com, a real
third-party site, with that site's demo login; it now uses
app.example.com and neutral credentials;
- the jump host examples used *.corp.local, and .local is the mDNS
domain, so copied settings resolve oddly; they now use
*.example.internal, as does the Squid proxy example;
- the move-entry response showed the folder "mrgelato"; now
"customer-a";
- the CLI audit log sample showed the user "dave"; now "alice", with
the columns kept aligned;
- the commented-out Knocknoc redirect in 403.http suggested
knocknoc.mycompany.com, a registered domain; now knocknoc.example.com.
Fixes#304
(cherry picked from commit b43de8678a)
The supported session types table for jump hosts listed only SSH, RDP,
VNC and web, although session creation also tunnels SPICE and Proxmox
VE. Add rows for them as the code behaves: SPICE forwards to the TLS
port when TLS is on, and Proxmox VE tunnels the PVE API and the SPICE
proxy separately, with certificate checks as they apply to each hop.
VDI is marked not applicable, since the container is local and the UI
hides the jump host section for it.
Also note how the network allowlists apply through a chain: the target
is resolved by the bastion, and only the first jump host is checked
locally, for ad-hoc sessions from non-admins.
Fixes#301
(cherry picked from commit 15390667b8)
On top of the workload JWT login from #285:
- token_command: a program and its arguments (run directly, never
through a shell) that prints the JWT, run again at every login, with a
timeout. For issuers that hand out a token on request, such as a cloud
metadata service. Its output never reaches an error or the log.
- import-guacamole and vault-migrate log in with jwt_auth too; they
insisted on VAULT_SECRET_ID. One helper, VaultConfig::login_secret,
now decides the secret for the server and both commands.
- jwt_auth is checked before any login: the mount goes into the login
URL, so plain path segments only, and exactly one of path and
token_command.
- Docs: the jwt_auth keys, and setting it up with Nomad workload
identity, SPIRE (spiffe-helper), and cloud VM identity tokens, with a
note on when JWT login really removes the stored secret.
Covers the claim-vs-scope distinction that trips Entra setups when users
copy the Authentik example. Includes the AADSTS650053 error explainer
plus a troubleshooting section. Promised in #153.
Closes#123 (parts 2 + 3 only; upload disconnect and drag-drop UX
postponed for further testing).
cleanup_browser hardcoded retention_secs=0 and never consulted
cleanup_on_close, so the [drive] config flags were dead code at
end-of-session teardown. This was visible to operators as
"cleanup_on_close = false has no effect" and "retention_secs = 0 wins
over cleanup_on_close = false" - the same root cause for both reports.
- cleanup_browser now takes (cleanup_on_close, retention_secs) and
only calls drive::cleanup_session_dir when cleanup_on_close = true,
passing retention_secs through. session.drive_path is still cleared
either way so subsequent reads don't think we still own the dir.
- drive_cleanup_settings() centralises the resolution from the
optional [drive] config; absent config uses the legacy "always
wipe immediately" defaults so installs that never enabled drive
keep prior behaviour.
- All four cleanup_browser call sites updated (delete, complete,
error, pending-timeout).
- Three new unit tests covering the resolution helper.
- Docs gain a "Cleanup behaviour" subsection in the Drive section
explaining the precedence (retention_secs only matters when
cleanup_on_close=true) and the per-session UUID subdir model that
means files do not persist across sessions even with cleanup
disabled. The cross-session "personal drive" model would be a
separate feature.
Closes#122.
Authentik does not ship a `groups` scope mapping by default, so the
existing instruction "Add the `groups` scope" in the provider's Advanced
protocol settings has nothing to select. Operators following the guide
end up with rustguac unable to read group memberships, which silently
breaks group-to-role mapping.
Adds a new step 1 explaining how to create a Scope Mapping under
Customisation > Property Mappings with the standard
`request.user.ak_groups` expression, and updates the provider step to
reference the newly-created mapping by name. Subsequent steps renumbered.
The Connections UI is the main user-facing rustguac feature, and it does
not work without Vault or OpenBao. The previous docs framed this as
"recommended" or "optional Vault-backed connections", which understated
how much of rustguac depends on the secret store being there.
- README.md gains a Requirements table listing guacd (bundled),
vault/openbao (required for Connections), OIDC (optional), Docker
(optional). The Features bullet now points at it.
- docs/installation.md upgrades step 4 from "(Recommended)" to
"(Required for connections)" with explicit "without one of these,
the Connections UI is unavailable" wording. Includes the
quickstart one-liner and a --local example.
- docs/overview.md drops "optional" from the Vault-backed connections
description and adds a follow-up paragraph pointing at the
integrations doc and the quickstart script.
- docs/integrations.md adds a Quickstart subsection above the manual
walkthrough with a mode table, three example invocations, and the
--local on-disk-unseal security caveat reiterated where it'll be
seen by anyone reading the section.
### OIDC discovery error wrap
src/oidc.rs now reshapes the openidconnect crate's "unexpected issuer
URI `X` (expected `Y`)" validation error into something an operator
can act on without having to reason about which side is the config
and which is the provider. For the overwhelmingly common
trailing-slash case the message collapses to a single actionable
line; for the rarer case where the two URIs differ materially (wrong
tenant, copy-pasted authorise URL, Keycloak path change), it falls
back to a config/provider/fix three-liner. Raw Debug output is
preserved for any non-mismatch discovery failure. Works for any
OIDC provider, not just JumpCloud.
### xrdp TLS key permissions (contrib/setup-xrdp-gfx.sh)
After rebuilding xrdp from sid, the `adduser xrdp ssl-cert` step
from the Debian postinst doesn't always re-apply cleanly, leaving
the xrdp user unable to read /etc/xrdp/key.pem. xrdp then falls
back to "classic RDP security" and FreeRDP drops the connection
with a MAC checksum error. New Step 9 in Phase 3 explicitly adds
xrdp to ssl-cert and normalises the key's ownership/mode. Step
list in the help text renumbered to match.
### Aurora default when [theme] is absent
src/main.rs resolved the theme via ThemeConfig::resolve() only when
config.theme was Some(..), and otherwise fell through to a hardcoded
("dark", builtin_presets()[0].1.clone()) pair. That meant aurora
only kicked in when a [theme] section was present in config.toml,
even an empty one. ThemeConfig now derives Default (all fields
Option<String>, so zero-cost), and main.rs resolves via
config.theme.clone().unwrap_or_default().resolve() so absent and
empty [theme] behave identically. Regression test added.
### docs/reverse-proxies.md
New doc covering nginx, Caddy, Apache, and Traefik with per-proxy
configs and the %2F-decoding gotcha that causes 404s on nested
subfolder paths when a proxy normalises the URI before forwarding.
The issue was surfaced by @mauroparente in #105 — thanks for the
repro and the nginx config that made the root cause obvious.
deployment-guide.md Step 3 and integrations.md HAProxy section
both link to the new doc.
Three pieces of v1.6.0 work that happened together and are easier to
review as one save point.
Rename: Address Book → Connections
- static/addressbook.html renamed to static/connections.html
- Nav links, page titles, empty states, onboarding, and prose updated
across all 8 static pages (connections, admin, docs, index,
recordings, reports, sessions, tokens).
- README, CLAUDE.md, and every file under docs/ updated.
- src/main.rs: connections.html added to the branded-page map and
route list; /addressbook.html returns a 308 permanent redirect so
existing bookmarks keep working.
- Backend API paths, Rust types, and Vault storage paths are
deliberately unchanged — internal only.
Folder allowed_groups picker
- New SQLite table `seen_groups` tracks OIDC groups observed in any
user login; OIDC callback upserts after extracting groups.
- `GET /api/auth/known-groups` (admin-only) returns the union of
group_role_mappings and seen_groups.
- `GET /api/addressbook/folders/{scope}/{folder}/config` adds the
missing endpoint the frontend was already calling — existing
allowed_groups now prefill the edit-folder modal.
- Folder modal swaps the free-text comma-separated input for a chip
picker with a themed combobox dropdown: autocomplete over known
groups, keyboard nav, "+ add custom" row for unlisted groups.
Active session visibility (GitHub #102)
- `GET /api/sessions` scopes to the caller's own sessions by default;
`?all=true` lets admins opt in (used by the Sessions page).
- `GET /api/sessions/{id}` and the thumbnail GET/PUT endpoints are
now owner-or-admin, returning 404 for other callers so session
existence isn't leaked.
- Connections' Active Sessions strip is now always owner-scoped —
admins still manage everyone via the Sessions page.
- New docs/web-sessions.md: comprehensive guide with autofill, domain
allowlisting, login scripts (Playwright + shell examples), clipboard
control, API reference, and troubleshooting
- Fix integrations.md: web sessions DO use credentials (for autofill
and login scripts)
- Add global Chromium policy warning to security.md and web-sessions.md
- Add web-sessions.md to in-app docs (build.rs)
- Condense overview.md web section to link to new dedicated doc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New features:
- Native Chromium autofill: pre-populate Login Data SQLite before launch,
zero external deps (no Node.js/Playwright needed for simple login flows)
- Per-entry domain allowlisting: restrict which domains Chromium can reach
via --host-rules (separate from server-side web_allowed_networks CIDR)
- Per-entry clipboard control: disable-copy and disable-paste for all
session types (SSH, RDP, VNC, Web) via guacd native parameters
- Guacamole import: `rustguac import-guacamole` parses mysqldump SQL and
writes entries to Vault address book
Security hardening:
- Comprehensive Chromium managed policy deployed via install.sh, Dockerfile,
and debian/postinst (blocks DevTools, downloads, file dialogs, extensions,
dangerous URL schemes)
- Profile isolation: each web session gets a unique UUID-based profile dir
- Autofill credentials encrypted with Chromium's native os_crypt (AES-128-CBC)
Documentation:
- Updated README, docs/api.md, docs/security.md, docs/configuration.md,
docs/overview.md, docs/integrations.md with all new features
- Clarified two-layer domain restriction (web_allowed_networks vs allowed_domains)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multi-hop SSH tunnel chains allow routing any session type through
multiple bastion hosts. VNC is now a first-class session type.
Web browser sessions can tunnel through jump hosts with automatic
URL rewriting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Patch guacd with Kerberos NLA support (002-kerberos-nla.patch),
based on upstream GUACAMOLE-2057 PR #581, adapted for FreeRDP 3.x
- Add per-entry auth_pkg, kdc_url, and prompt_credentials settings
to the address book (configurable in admin UI)
- Frontend credential prompt for entries without stored credentials
or with prompt_credentials enabled (never stored, session-only)
- Wire auth-pkg, kdc-url, kerberos-cache params through rustguac
to the guacd RDP handshake
- Comprehensive Kerberos NLA docs: krb5.conf setup, KDC discovery
options, FQDN requirements, troubleshooting guide
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>