50 Commits

Author SHA1 Message Date
Dave Kempe a57581ceef feat(themes): load themes from static/themes/*.toml at runtime
Themes were a Vec hardcoded in src/config.rs (builtin_presets()) -
every new preset required editing Rust, recompiling, and shipping a
new release, for what is purely presentation data. PR #148 from
@dav0l surfaced this nicely by failing to compile on a brace count
in the array.

This change adds a config::load_themes(&static_path) loader that
starts from the eight built-in presets (unchanged) and then merges
in any *.toml files from <static_path>/themes/. Disk themes can
add new entries or override a built-in by using the same name; the
existing builtin_presets() remains as the always-available fallback
when the themes directory is missing or empty.

File format: flat TOML table, one file per theme, filename (minus
extension) is the theme id. See static/themes/catppuccin-macchiato.toml
for a full example. Theme names are validated against the same
allowlist we use for Vault entry names ([a-zA-Z0-9_-]{1,64}) so they
are safe to render in the UI picker and in log lines, and can't be
used for path traversal or homoglyph mischief via crafted filenames.

dav0l's Catppuccin Macchiato palette lands here as
static/themes/catppuccin-macchiato.toml - their submission is the
first user-contributed theme under the new mechanism. Closes #148.

Backward compatibility: explicit. ThemeConfig::resolve() is now a
thin wrapper over resolve_with(builtins), so existing test callers
and any production callers see no behavioural change. Existing
[theme] sections in user config.toml files - preset only, preset +
overrides, overrides only, empty section, typo'd preset - all
resolve byte-equal to 1.7.0 (verified by the new
existing_user_config_with_theme_section_keeps_working_after_upgrade
test). Eight new tests in total cover the loader, the override
behaviour, the filename validation, and the upgrade scenario.

Docs broken out: themes get docs/themes.md (the full reference);
docs/configuration.md is trimmed to a brief stub and pointer.

No build-system changes needed - debian/rules, install.sh and the
Dockerfile all use recursive `cp -r static/` so the new themes
subdirectory is picked up automatically.
2026-05-29 10:29:13 +10:00
Dave Kempe 89129a54b5 fix(connections): hide subfolders the user cannot access
Closes #147.

ab_list_subfolders checked access to the parent folder but then returned
every subfolder unfiltered, so a user who could open a parent saw all of
its children regardless of per-child group ACLs - clicking one they
weren't entitled to gave "no access". (Top-level folders were already
filtered; this was the subfolder gap.)

Subfolders are now filtered per child. A folder is shown if the user can
access it directly OR can access any descendant of it, so a deeper grant
(child with its own allowed_groups and inherit_from_parent=false under a
denied folder) is never orphaned out of the tree. Admins still see all.

New folder_or_descendant_accessible helper does the recursive (boxed
async) OR over resolve_folder_access, short-circuiting on the first
accessible folder. No Vault mock harness exists to unit-test the
Vault-backed path; verified via build + the logic being a thin recursive
wrapper over the already-shipping resolve_folder_access.

docs/roles-and-access-control.md gains explicit notes that inaccessible
folders are hidden (not shown-then-denied) at every level, the
descendant-visibility rule, and how inheritance interacts.
2026-05-28 06:43:43 +10:00
Lindsay Harvey 66bc3722fc feat(vdi): host port range + container lifecycle hooks (#137)
Contributed by @vk2amv (Lindsay). Adds two related operator features to the Docker VDI driver:

1. Bounded host port range via [vdi] port_range_start / port_range_end. Currently Docker picks an arbitrary high port for the container's RDP listener; this lets operators constrain it which matters for firewalls, reverse proxies, and identity-aware gates that need to know in advance which ports rustguac will use. Port selection inside the range is deterministic-from-username (FNV-1a hash), so reconnects from the same user get the same port. Falls through to the next port on collision.

2. Container lifecycle hook script via [vdi] container_hook_script. Called as 'up <port> <container_id> <container_name>' after Docker assigns the port, again as 'down ...' before removal. Lets deployments wire external preparation/cleanup (firewall opens, service mesh registration, identity-aware gates) without baking the logic into rustguac itself. Bounded timeout (default 10s). Script is invoked via Command::new (no shell), so no injection risk from container metadata.

3 new tests covering port-candidate behaviour. Docs in docs/configuration.md and docs/vdi.md.

Thanks Lindsay.
2026-05-20 18:52:22 +10:00
Dave Kempe 3a37cb39d9 feat(vdi): per-entry container username/password override
Closes #132.

VDI containers come in two patterns: ones whose entrypoint reads
VDI_USERNAME / VDI_PASSWORD env vars and provisions an account
matching them (the contrib/vdi-test-image style), and ones with a
baked-in fixed account that ignore those env vars. Pre-change, only
the first worked; users with baked-image containers had to log in
manually inside the session because rustguac's auto-derived RDP
credentials never matched the container's actual account.

  - AddressBookEntry gains optional container_username and
    container_password fields, persisted to Vault.
  - When set, session.rs uses those values for the RDP connect into
    the container instead of deriving the username from the
    operator's identity and generating a per-connect password.
  - VDI_USERNAME / VDI_PASSWORD env vars are still injected with the
    resolved values - images that read them get consistent state;
    images that ignore them keep using their baked-in account.
  - The container name derives from the resolved username, so an
    entry with a fixed container_username produces a container
    shared by all operators connecting through that entry. Documented.
  - EntryInfo exposes container_username back to the editor;
    container_password is never serialised to clients (has_container_password
    boolean indicates whether one is stored).
  - The entry update handler preserves container_password when not
    supplied on update (same pattern as password / private_key).
  - Both fields go through entry_credential_variables and
    resolve_credential_variables, so the actual values can be sourced
    from each operator's saved credential variables ($corp_username etc).
  - Connections UI gains the two fields with explanatory text linking
    out to the VDI docs and credential variables docs.
  - docs/vdi.md restructured around the two patterns (Pattern A:
    env-var driven, Pattern B: baked-in account) with the
    container-sharing note for Pattern B.
  - docs/credential-variables.md gains an explicit "where variables
    can be used" table covering the new fields.

Subtle side fix: env merge in session.rs used to call
env.entry(K).or_insert(V), which meant a user-supplied
VDI_USERNAME in container_env would silently win over the
auto-derived one - opposite of the documented intent
("Don't let user-provided env override the core VDI vars").
Switched to env.insert() so the resolved values always win.
2026-05-18 20:21:41 +10:00
Dave Kempe 70763932ca fix(drive): honour cleanup_on_close and retention_secs at session end
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.
2026-05-06 15:57:26 +10:00
Dave Kempe 4aa18e1f32 docs(authentik): add missing groups scope mapping step
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.
2026-05-06 15:57:26 +10:00
Dave Kempe c7554fec1d docs: emphasise vault/openbao as a dependency, point at quickstart
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.
2026-05-06 15:57:26 +10:00
Dave Kempe 8a25869606 docs/overview: fix Apache Guacamole stack description
The "Key differences" table claimed Apache Guacamole's runtime was
"Java (Tomcat + Spring)" and that its database options included
LDAP. Both are inaccurate.

Per the upstream apache/guacamole-client pom.xml, the Java stack is
Tomcat (or any servlet container) plus Google Guice for DI and
Jersey for the JAX-RS endpoints, not Spring. LDAP is an authentication
backend in Guacamole's extension model, not a database; the actual
database options are MySQL and PostgreSQL.

Other rows in the table reference behaviours we should also verify
on a follow-up pass (OIDC support via guacamole-auth-sso, per-entry
clipboard control, etc.) but those need direct checks against
upstream rather than a guess; deferring for now.
2026-04-26 20:28:45 +10:00
Dave Kempe 542474dc3a v1.6.6 polish: OIDC error clarity, xrdp TLS perms, aurora default, reverse-proxy docs
### 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.
2026-04-25 15:40:08 +10:00
Dave Kempe 0d69e8fed4 Rename Address Book → Connections; allowed_groups picker; session privacy (#102)
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.
2026-04-18 21:56:20 +10:00
Dave Kempe 77c7c535f4 Docs cleanup, default theme to aurora, Vault install guidance
- Switch default theme from dark to aurora across server config,
  all 9 static HTML pages, example config, and docs
- Fix theme docs: list all 8 presets (was 6, missing jaguar/aurora),
  add missing type_vdi_bg/type_vdi_fg fields
- Add Vault/address book setup as recommended post-install step in
  installation docs — the address book is the primary user-facing
  feature and requires Vault
- Renumber subsequent install steps
2026-04-16 16:54:25 +10:00
Dave Kempe 2e046ea4a8 OIDC: native CA trust, custom CA cert, TLS skip-verify, better errors
Switch reqwest from rustls-tls-webpki-roots to rustls-tls-native-roots
so the OS trust store is used by default — private CAs installed
system-wide now work without extra config.

Add ca_cert and tls_skip_verify options to [oidc] for environments
where the system store isn't available or for debugging TLS issues.
Improve OIDC discovery error formatting to surface the actual cause
(issuer mismatch, TLS failure, etc.) instead of opaque "Request failed".

Closes #94
2026-04-08 06:31:14 +10:00
Dave Kempe 318725e8db Config docs: add [recording] and [vdi] sections, missing fields
- [recording] section: enabled, max_disk_percent, max_recordings, rotation
- [vdi] section: full reference with all 8 fields, prerequisites link
- Added missing top-level fields: rate_limit, session_history_retention_days
2026-04-05 10:03:06 +10:00
Dave Kempe 6a2a759abf Docs: VDI prerequisites — Docker install and usermod instructions
Installation guide and VDI docs page now document that Docker must be
installed separately and rustguac user added to docker group manually.
2026-04-05 08:27:21 +10:00
Dave Kempe 985a9de45c Overview docs: add VDI references throughout
VDI mentioned alongside SSH/RDP/VNC/Web in: intro, why section,
similarities, architecture description, tunnel section.
2026-04-05 08:16:57 +10:00
Dave Kempe 9114e2b78e Add VDI to overview docs page
Architecture diagram, session type section, comparison table,
project structure, and docs index updated with VDI references.
2026-04-05 08:13:56 +10:00
Dave Kempe 3d39685f58 Add VDI documentation page
Comprehensive docs covering: config, image requirements, address book
setup, container lifecycle, persistent homes, active sessions,
per-entry settings, security notes.
2026-04-05 08:11:00 +10:00
Dave Kempe 33105d9670 Docs: RPM is build-from-source only, not pre-built 2026-03-30 21:35:06 +11:00
Dave Kempe 6188c026e3 Docs: Desktop Composition is Windows-only, not needed for xrdp 2026-03-30 21:19:34 +11:00
Dave Kempe bcb376e2d7 Update docs for merged setup script (single command, --desktop flag) 2026-03-30 21:18:05 +11:00
Dave Kempe 2c7feb697c Add documentation index to overview page 2026-03-28 22:35:23 +11:00
Dave Kempe 32e353ba49 Add deployment guide for production planning and setup 2026-03-28 22:33:58 +11:00
Dave Kempe 799cdefd57 v1.0.0: H.264 passthrough, per-entry toggle, Docker fix
H.264 passthrough for RDP via guac_display worker integration:
- Raw H.264 NAL units from xrdp x264 pass through to browser
  WebCodecs VideoDecoder, bypassing server-side decode/re-encode
- Frame queue preserves all H.264 frames across deferred flushes
- Per-entry toggle in address book (enable_h264 field)
- Keyframe gate ensures decoder initializes correctly
- Per-connection callback (no static global concurrency bug)
- Queue capped at 120 frames to prevent unbounded growth

guacd patch (004-h264-display-worker):
- display-priv.h: h264 frame queue on guac_display_layer
- display-plan.c: flush h264 queue during plan_apply, skip IMG ops
- display-layer.c: set_h264 API with queue append and cap
- rdpgfx.c: SurfaceCommand wrapper saves NAL before GDI handler
- settings.c/h: enable-h264 connection parameter, conditional GfxH264

Other changes:
- Docker: fix FreeRDP plugin path for drive/audio channels (#87)
- contrib/setup-xrdp-gfx.sh: full xrdp x264 rebuild from Debian sid
- docs/rdp-video-performance.md: H.264 passthrough documentation
- Address book UI: H.264 checkbox with dependency descriptions
2026-03-28 21:52:28 +11:00
Dave Kempe d2dbfe3b2e Add Windows RDP performance tuning script (contrib) 2026-03-24 20:52:51 +11:00
Dave Kempe 3bf1762d87 v0.9.0: RDP audio, GFX pipeline, video performance
Audio:
- RDP audio output now works through guacamole. Advertise audio/L16
  and audio/L8 mimetypes in the guacd handshake, and explicitly set
  disable-audio=false. Fixed mimetype mismatch that silently prevented
  guacd from creating audio streams.
- Browser AudioContext auto-resumed on user interaction (click/keydown)
  to comply with autoplay policy.

Video performance:
- Per-entry GFX pipeline toggle (enable_gfx) — enables RemoteFX codec
- Per-entry desktop composition toggle (enable_desktop_composition)
- Per-entry force lossless toggle (force_lossless) — PNG-only mode
- WebSocket proxy buffer increased from 8KB to 64KB
- Video Performance section in address book UI for RDP entries

Documentation:
- RDP Video Performance guide with Windows server tuning (AVC444,
  60fps, GPU encoding) and Linux xrdp setup (Debian 13)
- contrib/setup-xrdp-gfx.sh — automated GFX/H.264 setup for xrdp
- contrib/setup-xrdp-audio.sh — automated PulseAudio module build
2026-03-24 20:25:05 +11:00
Dave Kempe 7acbb302fa v0.8.5: Reports, session history, enriched recordings
Session history:
- New session_history SQLite table tracks all sessions with user,
  entry, folder, hostname, duration, and recording file
- Automatic cleanup via session_history_retention_days config (default 90)
- 8 new unit tests for session history DB functions

Reports page (poweruser+ only):
- Summary cards: total sessions, hours, unique users, active now
- Sortable/filterable session history table with pagination
- Top Connections and Top Users leaderboards
- CSV export for session history with filters

Enriched recordings:
- Recording .meta sidecars now include user, folder, entry name, session type
- Recordings API returns enriched metadata from .meta files
- Recordings page shows User, Entry, Folder columns

Other:
- Reports nav link on all pages, hidden for operator/viewer roles
- Reports documentation in docs/reports.md
- Onboarding wizard for new users (role-scoped, dismissable)
- Settings menu label visibility improvement
- Fixed stale credential variable test (hyphen support)
2026-03-22 20:36:26 +11:00
Dave Kempe 35f7dd519c Fix Docker config copy command in docs (#73)
Add --entrypoint cat to override the container's default entrypoint
which starts guacd/rustguac instead of running the cat command.
2026-03-21 07:52:57 +11:00
Dave Kempe 6bafc79d60 v0.8.0: Credential variables, bug fixes
Credential variables — address book entries reference $domain_username /
$domain_password instead of storing static credentials. Users fill in their
own values via My Credentials (gear menu), stored per-user in Vault KV.
All variables set → silent launch; missing → prompted. Hyphens allowed in
variable names. Docs section added.

Bug fixes:
- Rate limiting disabled by default; opt-in via rate_limit = true (#62)
- Docker: copy FreeRDP guac-common-svc plugins to runtime image (#64)
- Docker/install: add chromium-sandbox package for non-root web sessions (#61)
- Logo: skip redundant JS src= when server-side branding already set (#65)
- Sessions page: hide Open/Share buttons for non-active sessions (#63)
- Drive: expose drive_configured in /api/auth/status, warn in UI when
  [drive] not configured
- install.sh: verify FreeRDP plugin installation

UI polish:
- Nav bar: border separator + spacing between header and nav on all pages
- Address book: password show/hide toggle on all password fields
- Drive diagnostic logging (session.rs, websocket.rs, client.html)

Closes #61, #62, #63, #64, #65
2026-03-13 13:47:11 +11:00
Dave Kempe 364b6772e4 v0.7.1: Guacamole import CLI, migration docs
- Wire up import-guacamole CLI subcommand (--file, --folder, --scope, --dry-run)
- Fix non-UTF-8 SQL dumps crashing import (use lossy UTF-8 decoding)
- Add migration.md to embedded docs
2026-03-12 21:03:02 +11:00
Dave Kempe 0c98aba190 v0.7.0: Banner field, automation UI, fix CDP policy, login script filtering
- Add optional `banner` field to address book entries (shown before session
  starts, user must click Continue). No longer auto-populates from display_name.
- Restructure web entry form: username, password, login script, and autofill
  collapsed under a collapsible "Automation" section.
- Filter login scripts dropdown to .js/.sh/.py files only (skip package.json etc.)
- Fix CDP/login scripts: change DeveloperToolsAvailability policy from 2 (disabled)
  to 0. DevTools UI remains blocked by chrome://* URLBlocklist. Fixes login script
  automation that was silently broken by the v0.6.0 security hardening.
- Update Dockerfile, debian/postinst, install.sh with corrected policy.
- Update docs/security.md and docs/web-sessions.md.
2026-03-11 23:01:22 +11:00
Dave Kempe a28816eca6 Document [theme] section: presets, logo_url, colour overrides, branding example (#55) 2026-03-11 20:31:17 +11:00
Dave Kempe 5a5bd8ff75 Document Vault KV v2 metadata policy requirement for deletes (#54)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 20:05:35 +11:00
Dave Kempe 04111ab177 Clarify TLS config docs: no boolean toggle, presence of fields controls behaviour
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 07:43:19 +11:00
Dave Kempe df402944f3 v0.6.2: Fix mTLS identity parsing (#51), decouple server/guacd TLS (#49)
- Rewrite Vault mTLS client to build rustls ClientConfig directly,
  bypassing reqwest::Identity::from_pem() which fails with rustls
  backend for PKCS#8 keys from OpenBao/Vault PKI
- Make cert_path/key_path optional in [tls] — guacd TLS now works
  independently of server HTTPS (for reverse proxy setups)
- Add webpki-roots direct dependency for custom TLS config
- Add mTLS tests: PKCS#8 key, fullchain cert, tls_skip_verify
- Update docs: configuration.md, security.md

Closes #51, closes #49

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 07:37:45 +11:00
Dave Kempe ac77bfab98 v0.6.1: Vault mTLS support, comprehensive test suite (87 tests)
Vault/OpenBao mTLS:
- Add ca_cert, client_cert, client_key fields to [vault] config
- Supports custom CA certificates and mutual TLS authentication
- Clear error messages for missing files, invalid PEM, missing key
- Fixes #48 (OpenBao requiring client certificates)

Test suite (8 → 87 tests):
- vault: 13 tests (TLS client builder, config deserialization, name validation)
- auth: 11 tests (role hierarchy, effective role capping, XFF/trusted proxy, has_role)
- session: 12 tests (CIDR network checks, autofill JSON parsing, placeholder substitution)
- browser: 8 tests (Chromium password encryption, Login Data SQLite, RangeAllocator)
- config: 8 tests (preset resolution, theme overrides, defaults, vault config)
- api: 6 tests (HTML escaping, recording name path traversal protection)
- db: 7 tests (SHA-256 hashing, key generation, user groups parsing)
- import: 12 tests (already existed, now wired into module tree)
- protocol: 8 tests (already existed)

Other:
- Wire import.rs into module tree (fixes orphaned tests)
- Document mTLS config in configuration.md and integrations.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:44:46 +11:00
Dave Kempe b5d31b13ce Add dedicated web sessions doc, fix credential docs, global policy warning
- 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>
2026-03-07 07:30:09 +11:00
Dave Kempe 99d79fe05d v0.6.0: Web autofill, domain allowlisting, clipboard control, Guacamole import
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>
2026-03-07 00:54:26 +11:00
Dave Kempe 2bf34440e6 v0.5.1: RDP resize fix, new themes, Docker config persistence
- Fix RDP display resize for FreeRDP 3.x (patch 004: config.h struct layout)
- Add aurora theme (midnight blue with ambient glow gradients)
- Add jaguar theme (racing green & gold with subtle gradients)
- Add bg_pattern support for CSS gradient backgrounds in themes
- Fix Docker config.toml persistence across rebuilds (#38)
- Add Docker Compose volume mount documentation
- Increase API rate limit to 5/s burst 30 (fix spurious 429s)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:08:14 +11:00
Dave Kempe ea72c52a31 v0.5.0: Theme system, ARM64 builds, Docker fixes, dependency updates
Theme system:
- 6 built-in presets (dark, light, high-contrast, terminal, nord, corporate)
- Admin configures preset + per-color overrides in [theme] config section
- Client-side theme switching via localStorage (flash-free)
- All static pages updated with 28 CSS custom properties

Proxy telemetry:
- Track which side terminated connection (guacd/browser/cancelled)
- Timing-based log levels (guacd close <5s = warning)
- Clamp session dimensions to safe ranges (width 640-8192, height 480-8192, DPI 16-384)

Docker fixes (#37):
- Fix port mismatch: Dockerfile now uses 8089 consistently
- Auto-generate admin API key on first run
- Add API key setup docs and recordings volume to compose example

ARM64 support:
- Multi-platform Docker builds (linux/amd64 + linux/arm64)
- Native ARM64 .deb and tarball builds via ubuntu-24.04-arm runner

Dependency updates:
- rustls 0.23.37, chrono 0.4.44, clap 4.5.60, toml 1.0.3
- futures-util 0.3.32, uuid 1.21.0, pulldown-cmark 0.13.1
- actions/upload-artifact v7, actions/download-artifact v8

Also: FreeRDP 3.x NULL deref patch (003), .gitignore for .playwright-mcp/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:05:41 +11:00
Dave Kempe 7f454ecda0 Fix custom link port parameter: omit when unset instead of sending empty string
An empty &port= causes "invalid digit found in string" parse error.
Use {% if %} conditional to only include &port= when remote_port is set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:26:23 +11:00
Dave Kempe 3cf21933ea Add credential prompting for /api/connect deep-links, fix NetBox webhook docs
When an address book entry has prompt_credentials: true or no stored
credentials, /api/connect now returns an inline credential form instead
of failing or connecting without auth. The form POSTs to the existing
connect endpoint and redirects to the client page.

Fix NetBox webhook body template docs: use "type" not "session_type"
(matches Vault storage format), replace regex_replace/cut filters with
standard Jinja2 equivalents (lower, split) since NetBox's Jinja2
environment doesn't include Ansible or Django template filters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:14:53 +11:00
Dave Kempe 5331f25176 Add NetBox integration: GET /api/connect endpoint, OIDC deep-links
- New GET /api/connect quick-connect endpoint for external integrations
  - Ad-hoc mode (poweruser+): hostname/protocol/port params, redirects to client
  - Address book mode (operator+): scope/folder/entry params, credentials from Vault
  - Unauthenticated users redirected through SSO login and back automatically
- OIDC deep-link support: login handler accepts ?next= param, stores as cookie,
  callback redirects there instead of /addressbook.html after authentication
- New docs/netbox.md integration guide: Custom Fields, Custom Links with
  console_enabled/console_mode gating, webhook-driven address book sync
- Updated docs/api.md with GET /api/connect endpoint documentation
- Updated screenshots/screenshots.md with descriptions for all screenshots
- Bump version to 0.3.1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 11:52:16 +11:00
Dave Kempe 76c93bc502 Add multi-hop SSH tunnels, VNC sessions, and web session tunnel support
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>
2026-02-11 08:22:34 +11:00
Dave Kempe d07d79c4f2 Add Kerberos NLA support for RDP and credential prompting
- 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>
2026-02-10 10:39:18 +11:00
Dave Kempe a94b743d6c Add user API tokens with role-based access and audit logging
User API tokens allow OIDC users to authenticate via bearer token for
automation and scripting. Powerusers and admins can create their own
tokens; admins can create tokens for operators. Tokens use SHA-256
hashing, optional max_role caps, optional expiry, and full audit
logging of create/revoke operations with client IPs.

- DB schema: user_api_tokens and token_audit_log tables
- Auth middleware: validates user tokens as fallback after admin keys
- API: 7 new endpoints (self-service + admin token management)
- UI: tokens.html (self-service) + admin.html token/audit sections
- Nav: Tokens link added to all pages (visible for operator+)
- Docs: API reference, security model, roles/access control updated
- Background cleanup: expired tokens + 90-day audit log retention

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 15:02:54 +11:00
Dave Kempe 466ffd2d44 Upgrade openidconnect v3 -> v4, remove JumpCloud references
- 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>
2026-02-07 10:39:24 +11:00
Dave Kempe 292db1fa55 Update OIDC docs to prefer Authentik, add setup guide
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 09:52:23 +11:00
Dave Kempe 6bf8383e88 fixes bugs #1 #2 and #3. 2026-02-07 09:32:05 +11:00
Dave Kempe 439eab21ee Update Docker references to sol1/rustguac on Docker Hub
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 15:15:02 +11:00
Dave Kempe 67101e27ce Initial public release
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 14:38:53 +11:00