255 Commits

Author SHA1 Message Date
Dave Kempe 98b634f438 Fix: delete_folder recursively removes subfolders
Prior behaviour: delete_folder only cleared entries and .config of
the named folder; subfolder .config keys were left behind. Pre-v1.6.0
that was fine because subfolders didn't exist. Post-subfolders the
UI would refresh after delete, list_children would still find the
orphaned subfolder markers, and the folder would appear stuck (the
DELETE request returned 204, no error surfaced, nothing looked
broken except the folder refused to go away).

delete_folder now BFS-collects every folder path in the subtree
before wiping entries and .config at each level. Return type changes
from Result<(), _> to Result<(usize, usize), _> so the endpoint can
report (subfolders_deleted, entries_deleted) back to the UI; the
single caller in api.rs is updated.

UI: confirmation prompt now explicitly mentions "AND all its
subfolders and entries" when the selected folder has_children is
true, so admins don't nuke a subtree by accident. After a successful
delete, a transient banner reports the number of entries (and
subfolders when > 0) that got swept up.
v1.6.3
2026-04-21 15:05:14 +10:00
Dave Kempe 58a8f36099 v1.6.3 2026-04-21 14:50:28 +10:00
Dave Kempe 2d554c731d Folder permission inheritance + import ACL flags
FolderConfig gains `inherit_from_parent: bool` (default false via
#[serde(default)] so existing deployments keep their per-folder-only
semantics). When true, if a folder's own allowed_groups doesn't grant
the caller, the access check walks up the slash-separated path and
evaluates each ancestor the same way. Admins still bypass all checks.
Inheritance stops at any folder with the flag off, preserving the
ability to lock down a specific subtree.

New resolve_folder_access helper in vault.rs centralises the walk-up
logic; check_folder_access, ab_list_folders, and ab_list_all in
api.rs all route through it so access semantics live in one place.

Import: new --allowed-groups flag writes the ACL onto the root import
folder only; subfolders are created with inherit_from_parent=true so
the whole imported tree picks up the same rules without per-folder
writes. Matches the pattern admins will want after an initial
Guacamole migration where every connection group should be visible
to the same OIDC group.

Related fix: list_credential_variables in api.rs no longer scans only
top-level folders — it now recurses into subfolders so variable
references buried in an imported tree show up on the My Credentials
page (previously they stayed invisible post-v1.6.0 when subfolders
became first-class).

UI: folder modal gets an "Inherit permissions from parent folder"
checkbox. Defaults: unchecked for new top-level folders (no parent
to inherit from), checked for new subfolders, and reflects the
stored value when editing.
2026-04-21 14:42:03 +10:00
Dave Kempe 37837fa247 cargo fmt 2026-04-21 12:46:46 +10:00
Dave Kempe 51531c8c8c v1.6.2 v1.6.2 2026-04-21 12:40:42 +10:00
Dave Kempe d052042bd1 Fix three bugs found in internal testing
- db: auto-repair db file ownership when init_db runs as root against a
  data dir owned by a non-root user. Fixes "attempt to write a readonly
  database" after operators run `rustguac add-admin` under sudo, which
  previously left the sqlite file owned by root:root and unwritable by
  the rustguac service user.

- import-guacamole: preserve Guacamole connection-group hierarchy as
  real Vault subfolders instead of flattening into hyphen-joined entry
  names. Each ancestor group gets its own .config so empty parents
  still render in the Connections tree. Per-folder dedup so the same
  connection name in two groups no longer collides.

- config: fatal, loud, line-annotated error on malformed config.toml.
  Previously a TOML parse error emitted a tracing::warn before the
  subscriber was initialised and silently fell back to built-in
  defaults, leaving the operator debugging a "working" server that
  ignored their entire file. Errors now print via eprintln with the
  toml crate's column-pointed snippet and exit(1) whenever a config
  path was explicitly given (or /opt/rustguac/config.toml exists).
2026-04-21 12:39:14 +10:00
Dave Kempe 65a0c42650 Refresh screenshots for v1.6.1
Re-captured from sol1-remoteconsole running 1.6.1 with the default
aurora theme and the new Connections layout (renamed from Address
Book in v1.6.0). User-identifying data sanitised; a temporary demo
folder with clearly-fake entries (example.com, saucedemo, internal
bastion hops) was seeded and torn down around the capture.

New views added:
 - connections.png — subfolder tree + scope icons, all five entry
   type badges
 - vdi_connection.png — Docker VDI editor showing container image,
   CPU/memory limits, allow_sharing + auto_open_if_singleton toggles
 - reports_view.png — aggregate counters + filterable session history

Refreshed:
 - rdp_connection.png — NLA auth pkg, KDC URL, RemoteApp, recording
 - web_connection.png — banner, autofill with $USERNAME/$PASSWORD,
   allowed domains
 - ssh-tunnel.png — multi-hop flow diagram with jump host cards
 - sessions_view_with_adhoc.png — collapsible ad-hoc form + active
   sessions panel
 - recordings_view.png, recordings_player_histogram.png
 - admin_console.png — v1.6.1 badge, system-status cards, users table
 - tokens_view.png — group-to-role mappings + user API tokens

Removed screenshots/address_book.png (renamed to connections.png
as per the v1.6.0 rework).
2026-04-19 17:17:50 +10:00
Dave Kempe fd42f39604 v1.6.1 v1.6.1 2026-04-19 14:48:06 +10:00
Dave Kempe 9dbdc17843 Security hardening + test harness expansion
Security audit (v1.6.1 cycle) findings:

- Shadow tokens: each use now writes a shadow_used entry to
  token_audit_log with the connecting IP. Prior behaviour audited
  only the mint, so a leaked token could be replayed within its
  10-minute TTL with no observable record.

- OIDC groups claim: bound per-name length to 256 bytes (UTF-8
  boundary preserved) and array size to 64. A misconfigured or
  compromised IdP could previously bloat the seen_groups table
  unbounded.

- .cargo/audit.toml formalises the rsa (RUSTSEC-2023-0071) and
  rustls-pemfile (RUSTSEC-2025-0134) advisory ignores with
  rationale, replacing the inline --ignore flag in CI. New
  advisories on those crates will still surface.

Small defence-in-depth: WebSocket Origin/Host compare is now
case-insensitive (DNS is canonical) — previously exact-match.

Test harness grows from 150 to 215 tests:

- Pure-function regression coverage: OIDC groups cap, shadow-token
  validation matrix (owner/shadow/invalid/expired/IDOR), Vault
  path traversal, VDI username sanitization, recording-name
  hardening, JPEG magic bytes, Guacamole protocol parser
  adversarial cases (overflow length, UTF-8 split, malformed
  frames, buffer cap, streaming boundaries), Origin/Host matcher.

- Async + in-memory state: SessionManager test helper bypassing
  disk/browser, owner/shadow validation end-to-end, mint prunes
  expired, shadow is session-scoped (IDOR guard), disconnect_viewer
  saturating decrement, seen_groups DB bounds, rate-limit layer
  burst -> 429 (proves tower_governor is actually applied).

Behaviour-preserving refactors to support testing:
check_share_token_match (from validate_share_token),
is_jpeg_magic (from thumbnail PUT), origin_host_matches (from
ws_handler CSWSH check).
2026-04-19 14:46:07 +10:00
Dave Kempe c21131b1e1 release: split Docker build to native runners + refresh locks
Docker pipeline
- Multi-arch build on ubuntu-latest with QEMU was the long pole of
  the release pipeline (arm64 took 30–60 min vs ~5 min for the
  native-arm .deb job). Split into three jobs:
  - build-docker-amd64 on ubuntu-latest
  - build-docker-arm64 on ubuntu-24.04-arm (same native runner the
    arm64 .deb build uses)
  - build-docker-manifest combines them with
    `docker buildx imagetools create` into the consumer-facing
    `sol1/rustguac:VER` and `:latest` multi-arch manifest lists.
- Consumer-facing tags are unchanged — `docker pull
  sol1/rustguac:latest` still auto-picks the right arch. The per-arch
  intermediate tags (`:VER-amd64`, `:VER-arm64`) appear as byproducts
  on Docker Hub but aren't required.
- The release job now depends on build-docker-manifest instead of
  the removed build-docker.

Dependency refresh (closes 5 low-severity Dependabot alerts)
- `cargo update` at root and in fuzz/:
  - rustls-webpki: fuzz/ was 0.103.x < 0.103.12 → now 0.103.12
    (main lock was already there from v1.5.5)
  - rand 0.9.x: < 0.9.3 → 0.9.4 (GHSA-cq8v-f236-94qc: unsound with
    a custom logger inside rand::rng())
  - rand 0.10.0 → 0.10.1 (same GHSA)
- We don't hook loggers into rand so the unsoundness never triggered
  in practice, but getting to clean alerts is worth a lockfile bump.
v1.6.0
2026-04-18 22:21:47 +10:00
Dave Kempe a0c582dd45 cargo fmt 2026-04-18 22:08:08 +10:00
Dave Kempe f9b3475be2 v1.6.0 2026-04-18 22:02:12 +10:00
Dave Kempe e791383375 Issue #103: auto-open singleton entry + client Home button + group picker fix
Feature #103: single-entry auto-connect
- New `auto_open_if_singleton: Option<bool>` on AddressBookEntry and
  EntryInfo. Admin ticks it per entry in the Connections modal;
  importer initialises to None.
- After the /api/addressbook batch resolves on the Connections page,
  if the user sees exactly one entry and it has the flag set, the
  page fetches /connect and navigates the current tab to the new
  session. Same-tab navigation (not a popup) because browsers block
  window.open after an async fetch without a user gesture.
- A sessionStorage flag gates the auto-open to once per browser
  session — an accidental refresh of Connections doesn't re-spawn
  sessions. Logout clears the flag so the next login fires again.
- Admins never meet the singleton condition (they have many
  entries) so this is effectively kiosk-only.

Escape hatch: client.html Home button
- Ctrl+Alt+Shift panel header now has a 🏠 Home button next to the
  close ×. Takes the tab back to /connections.html — primary route
  for a kiosk user whose session auto-opened into this tab.

Bug fix: folder-modal group picker silently added a group on save
- addFmGroupFromInput used to fall through to picking the combo's
  highlighted suggestion when the input was empty. fm-save calls
  addFmGroupFromInput to flush any typed-but-not-chipped text; with
  an empty input that meant every Save on an existing folder tacked
  on the first unselected known group (e.g. an operator group) —
  visible only on the next edit. Split the "pick suggestion when
  empty" behaviour out to an explicit helper that only fires from
  Enter-with-empty-input, never from Save.
2026-04-18 21:56:20 +10:00
Dave Kempe 3b97dfbcdf Per-entry allow_sharing toggle + modal checkbox alignment fix
Per-entry Share gating
- AddressBookEntry gains an `allow_sharing: Option<bool>` (default
  None = off). Admin opts in per entry via an Allow session sharing
  checkbox in the entry modal.
- EntryInfo exposes the field so the modal prefills on edit.
- CreateSessionRequest carries it through; the ab_connect_entry
  quick-connect and legacy quick-connect paths propagate the entry's
  value.
- Session gets a share_allowed bool. SessionInfo.share_url is only
  populated when share_allowed is true, so the Connections
  Active Sessions Share button auto-hides without any new UI logic.
- Default behaviour on create:
    * explicit allow_sharing on the request → honoured
    * entry-derived session without flag → off (admin opt-in)
    * ad-hoc session (no address_book_entry) → on
  The ad-hoc default preserves the long-standing API-key
  session-creation flow where external callers expect share_url in
  the POST /api/sessions response.

Modal checkbox alignment (side-effect fix)
- The generic .modal input rule in rustguac.css was forcing every
  input — checkboxes included — to 44px height + block + 100% width,
  which misaligned every tickbox/label pair across the entry, folder,
  and onboarding modals.
- Added carve-outs for input[type=checkbox] / [type=radio] that keep
  native size, inline, with a small right margin.
- Labels that directly contain a checkbox/radio (via :has()) now flex
  the control + text on one baseline with a clean gap, and drop the
  uppercase letter-spacing used for full-width field labels.
2026-04-18 21:56:20 +10:00
Dave Kempe 4a4f3807f2 Terminate on active cards + warning banner in Share modal (stage 4)
Modified-stage-4: always-visible buttons, no hover-reveal.

- Each active session card now has a Terminate button next to Share.
  Confirmation dialog on click, DELETE /api/sessions/{id}, reloads
  the grid on success. Hover state uses .btn-danger (primary red).
- Share button still hides when share_url is absent so the upcoming
  per-entry allow_sharing toggle gates it automatically.
- Share modal's caution text was a dim .field-hint that was easy to
  miss. It now renders as a proper warning banner: 1px border +
  bumped left edge in --status-pending (theme-aware yellow), ⚠ icon,
  and larger body text for legibility.
2026-04-18 21:56:20 +10:00
Dave Kempe e8d19e38dd Sessions page role scoping + count header (stage 3)
Polish for the Sessions page now that it's the admin/poweruser
management view (share has moved to Connections, shadow landed in
stage 2).

- Page heading becomes a .section-head strip with a right-aligned
  count span ("12 active · 3 yours" for admins seeing others,
  "3 active" when the caller only sees their own).
- Empty state is role-aware: admins get the plain "No active
  sessions", non-admins get a nudge toward Connections / the ad-hoc
  form.
- Owner column is dimmed for own sessions and accent-teal bold for
  others — lets admins eye-scan own vs others at a glance.
- Delete button is hidden on rows the caller can't delete (non-admin
  viewing someone else's session). Backend check is still the
  authoritative gate; this just tidies the UI.
2026-04-18 21:56:20 +10:00
Dave Kempe c750053856 Bug fixes: no-duplicate active-session click + hide Sessions for viewers
Connections
- Clicking an active session card used to POST /connect for entries
  with an address_book_entry, which minted a new session every time.
  Confirmed in the log: a web-session owner clicked back to their
  active card after a shadow join and got two duplicate sessions.
- Now the click just opens s.client_url — which attaches to the
  existing session. Dormant VDI containers below still need /connect
  (no live session to attach to), so that branch is untouched.

Tokens page
- Operator/viewer roles saw the Sessions nav link on the Tokens page
  (every other page correctly hid it for level < 3). Clicking it
  redirected straight back out because sessions.html rejects level < 3.
- Added the same hide-on-level-<3 logic as the other pages; also
  reveal the Reports link for level >= 3 in the same pass.
2026-04-18 21:56:20 +10:00
Dave Kempe 635bc061cc Shadow sessions: admin read-only viewer tokens
Part of the shadow-sessions plan (stage 2). Admins can now join any
active session from the Sessions page without the user having to
share — the backend mints a short-lived token and every mint is
logged to token_audit_log.

Backend
- Session struct gets a shadow_tokens: Vec<ShadowToken> (sha256 hex
  of the raw token, issuing admin, expiry). Expired entries are
  pruned on mint.
- validate_share_token now accepts either the owner's share_token or
  any non-expired shadow token, so the existing viewer path works
  unchanged.
- POST /api/sessions/{id}/shadow (admin-only) mints a raw token,
  pushes its hash + a 10-minute expiry onto the session, writes a
  token_audit_log row (action = "shadow_session", details includes
  session_id, owner, expiry, caller IP) and returns the viewer URL.

Frontend (Sessions page)
- /api/me fetched on load so we know the caller's display_name and
  role before rendering. API-key users default to admin and fetch
  /api/me to learn their name.
- Own active sessions still render "open" (anchor to client_url).
- Others' active sessions render "shadow" as an anchor (not a button)
  so both action cells line up in the same column. Shadow uses
  --status-pending (warning yellow) instead of the neutral accent,
  hovers to --primary, and shows a "minting..." busy state while
  the POST is in flight.
2026-04-18 21:56:20 +10:00
Dave Kempe eb1521dd6c Share button moves to Connections Active Sessions
Part of the Sessions-page rework (shadow-sessions plan, stage 1).
The Sessions page is becoming an admin/poweruser management view;
user-initiated share lives with the user's own active sessions in
Connections.

Connections
- Each active session card gets a Share button in a new action row
  below the thumbnail/meta.
- Share opens a themed modal with the full share URL pre-selected
  and a Copy button (async clipboard with execCommand fallback).
- Clicking the card still reconnects — Share handler stops
  propagation so the two actions don't collide.
- Overlay click or Close dismisses the modal.

Sessions page
- Share column removed (was columns 10 of 10 — down to 9).
- Dead CSS (.btn-share, .share-url) and JS (expandedShares,
  shareFullUrl, rowId, the share/copy click handlers) removed.
- Ad-hoc jump-host styling (.btn-add-hop) stays.
2026-04-18 21:56:20 +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 88b754a11c Address book: subfolder tree UI + shared design system
Frontend for #101 — subfolder support (backend landed in c2a3822) — plus
a site-wide visual overhaul extracted into a single shared stylesheet.

Address book
- Folder sidebar renders as a tree with lazy-loaded children via the
  /api/addressbook/folders/{scope}/{path}/subfolders endpoint.
- Scope badge is now an icon with hover tooltip: ⊕ shared, ▣ instance.
- New "+ subfolder" button creates a nested folder under the selection.
- Move-entry dropdown includes any loaded subfolders.
- Batch folder API now returns path + has_children so the tree can
  render chevrons without a second request per folder.

Design system (rustguac.css)
- Extracted ~700 lines of near-duplicate CSS from each page into a
  shared stylesheet linked by every page.
- 18px body, strict 38/44/54px control heights, uppercase letter-spaced
  section labels, zebra table rows, active-nav underline bar.
- Uniform button ladder: primary (red) / accent (connect, teal) /
  ghost (+ buttons) / small (edit/delete chrome).
- Generic status colors, type badges, pagination, token-reveal,
  summary cards, hop cards, and flow diagram now live in one place.

Per-page updates
- addressbook, admin, docs, index, recordings, reports, sessions,
  tokens: style blocks reduced to page-specific layouts only.
- reports/recordings/sessions: bare <strong> page titles promoted
  to <h2> for proper heading hierarchy.
- Stripped inline padding/font-size attributes that were overriding
  the shared ladder.
2026-04-18 21:56:20 +10:00
dependabot[bot] 5143df15f4 ci: bump softprops/action-gh-release from 2 to 3 (#104)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-18 21:55:05 +10:00
Dave Kempe c2a382211a Address book: subfolder support backend (#101)
Add hierarchical folder support to the Vault-backed address book.
Folders can now be nested (e.g., Clients/Acme/Servers) using Vault
KV v2's natural path hierarchy.

- Add validate_path() for multi-segment folder paths, replacing
  validate_name() for folder parameters. Each segment validated
  individually — blocks traversal, reserved names, special chars.
- FolderInfo gains path and has_children fields for tree UI support
- New list_subfolders() and list_children() methods on VaultClient
- New GET /api/addressbook/folders/{scope}/{folder}/subfolders endpoint
- Existing flat folder operations unchanged (backward compatible)
- Client percent-encodes folder paths: Clients%2FAcme in URL decodes
  to Clients/Acme — no wildcard routes needed

Tested on sol1-remoteconsole: subfolder CRUD, entry CRUD in subfolders,
has_children detection, and existing flat folder compatibility verified.
2026-04-16 19:46:59 +10:00
Dave Kempe 8c248edb53 deps: update rustls-webpki 0.103.12 (RUSTSEC-2026-0098, 0099) v1.5.5 2026-04-16 17:34:10 +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 84616f5730 Session limits and completed session cleanup (#99)
- Add max_sessions (default 500) and max_sessions_per_user (default 50)
  config options. Session creation is rejected with a clear error when
  limits are reached. Set to 0 for unlimited (backward compatible).
- Add background reaper that removes completed/error/expired sessions
  from the in-memory HashMap after session_cleanup_delay_secs (default
  300s). Session history in SQLite is not affected.
- Prevents resource exhaustion from unbounded session creation and
  memory leak from accumulated completed sessions.

Closes #99
v1.5.4
2026-04-12 07:40:32 +10:00
Dave Kempe 58ba74ff51 WebSocket Origin check: compare hostnames only, ignore ports
Avoids false rejections behind reverse proxies where the Host header
may include an explicit port (e.g. :443) that the browser's Origin
omits as a default port.
2026-04-12 07:23:57 +10:00
Dave Kempe 2074895e90 Security hardening: 7 audit findings fixed
From OWASP-based security audit (categories 2-12):

- Vault path traversal: validate folder names on read operations
  (get_folder_config, list_entries, get_entry) — write operations
  already validated but reads did not
- SSH host key: reject connection when stored key fails to parse,
  instead of silently accepting (was bypassing verification)
- Config secret redaction: custom Debug impls for OidcConfig and
  VaultConfig that redact client_secret, role_id, and client_key
- Branding XSS: HTML-escape site_title and logo_url config values
  before injecting into page templates
- WebSocket CSWSH: validate Origin header against Host header on
  WebSocket upgrade, reject cross-origin requests
- VDI bind mounts: add nosuid,nodev mount options to home directory
  bind mounts to prevent setuid binary attacks
- Recording access: restrict list/serve endpoints to poweruser+ role
  (previously any authenticated user including viewers could access
  all recordings)
2026-04-12 07:19:16 +10:00
Dave Kempe 622569290a Security hardening: share URL redaction, auth rate limiting, fixes
- Redact share_url from session listings for non-owners — previously
  any authenticated user could enumerate share tokens and join sessions
  they didn't create (share_url now only returned to session creator
  and admins)
- Always rate-limit OIDC login/callback (1/sec burst 5 per IP)
  regardless of rate_limit config, preventing brute-force on auth
- Fix disconnect instruction detection: use instruction boundary
  matching instead of substring contains — clipboard content or typed
  text containing "10.disconnect;" could falsely trigger VDI container
  destruction
- Restrict sudoers chown rule to rustguac:rustguac only, preventing
  arbitrary ownership changes on the LUKS mount point
v1.5.3
2026-04-11 21:49:03 +10:00
Dave Kempe a330c0cda0 deps: bump bollard 0.18 to 0.20, update VDI Docker driver
Bollard 0.20 moved container option types from bollard::container to
bollard::query_parameters, replaced Config<T> with ContainerCreateBody,
and changed several fields to Option types. Update all imports and call
sites in the VDI Docker driver accordingly.
2026-04-11 21:11:38 +10:00
Dave Kempe 07dbf874bd Merge pull request #96 from sol1/dependabot/cargo/russh-0.60.0
deps: bump russh from 0.59.0 to 0.60.0
2026-04-11 17:48:37 +10:00
dependabot[bot] 8bdd5ab115 deps: bump russh from 0.59.0 to 0.60.0
Bumps [russh](https://github.com/warp-tech/russh) from 0.59.0 to 0.60.0.
- [Release notes](https://github.com/warp-tech/russh/releases)
- [Commits](https://github.com/warp-tech/russh/compare/v0.59.0...v0.60.0)

---
updated-dependencies:
- dependency-name: russh
  dependency-version: 0.60.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-11 07:47:40 +00:00
Dave Kempe 267f082f11 Merge pull request #97 from sol1/dependabot/cargo/tokio-1.51.1
deps: bump tokio from 1.51.0 to 1.51.1
2026-04-11 17:46:35 +10:00
dependabot[bot] c6f560d75d deps: bump tokio from 1.51.0 to 1.51.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.51.0 to 1.51.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.51.0...tokio-1.51.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.51.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 09:21:20 +00:00
Dave Kempe 22c3638d00 cargo fmt v1.5.2 2026-04-09 21:26:13 +10:00
Dave Kempe 958b675fd2 SSH tunnel: host key verification with UI-driven pinning
check_server_key no longer blindly accepts all keys. Jump hosts now
support a host_key field stored in Vault alongside credentials.

- New POST /api/ssh/probe-host-key endpoint probes an SSH server and
  returns its public key, fingerprint, and algorithm
- Address book UI: "Verify Host Key" button per jump host probes the
  server, shows fingerprint for confirmation, stores key on save
- TunnelHandler verifies the server key against the stored key on
  connect — rejects with detailed error on mismatch
- Unpinned keys accepted with TOFU warning log for backward compat
- host_key preserved through credential merging on entry update

Closes #95
2026-04-09 21:22:36 +10:00
Dave Kempe f43738f54a H.264: remove frame dropping, sync gating approach only
Frame dropping at queue depth 5 triggered during normal operation and
broke the H.264 reference chain, corrupting output until the next
keyframe. Remove frame dropping entirely — sync gating alone provides
sufficient flow control by delaying the sync response until decodes
complete, giving guacd accurate backpressure.
v1.5.1
2026-04-09 07:59:03 +10:00
Dave Kempe 175c30b705 H.264: fix unbounded stream lag via sync gating and frame dropping
The H.264 decode path bypassed the Guacamole sync mechanism — the sync
response fired immediately without waiting for WebCodecs to finish
decoding. guacd thought the client was keeping up and sent at full rate,
causing the decode queue to grow without bound (30+ seconds of lag
observed over time).

Fix: gate the sync response on H.264 decode completion so guacd gets
real backpressure. Also drop delta frames when the decode queue exceeds
5 frames as a safety valve for transient overload (tab backgrounding).

- Add pending-decode tracking and per-frame position capture to
  H264Decoder.js (fixes shared mutable state race)
- Add waitForPending() with 1s safety timeout for sync gating
- Add frame dropping when decodeQueueSize > 5 (never drops keyframes)
- Add stats() method for console debugging (__guac_client._h264Decoder.stats())
- Gate sync response in Client.js on H.264 decode completion

Closes #93
2026-04-08 07:21:23 +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 5c7eb84886 README overhaul: badges, VDI, updated features, remove RHEL
- GitHub badges (CI, release, license, Docker pulls)
- VDI featured in architecture, features, and quick start
- Feature tables for session types, security, connectivity
- Removed RPM/RHEL references (build from source for others)
- Complete docs index with all current pages
- Cleaner quick start with Docker+VDI instructions
2026-04-06 08:25:00 +10:00
Dave Kempe 75eae2b43f cargo fmt v1.5.0 2026-04-05 10:33:01 +10:00
Dave Kempe fb0f965bb2 Fix nav: Logout + Settings float right together on all pages
Logout link moved inside the float-right user-menu-wrapper so both
Logout and Settings appear on the far right of the nav bar. Fixed
addressbook.html Settings font size to match other pages (1.3em).
2026-04-05 10:20:10 +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 c48be9ed3f Move Logout button out of Settings dropdown on all pages
Logout is now a standalone nav link next to Settings (Logout Settings
order, right-aligned). Applied consistently across all 7 HTML pages.
Removed Logout from the settings dropdown menu.
2026-04-05 10:01:35 +10:00
Dave Kempe a9a966e675 VDI: detect image change and clear stale thumbnails
- start_or_reuse checks if existing container's image differs from
  the requested image. If so, stops the old container and creates
  a new one with the new image.
- Clear stale VDI thumbnail when starting a new session, so old
  screenshots don't linger from previous images/failed sessions.
2026-04-05 08:33:42 +10:00
Dave Kempe 7283cdb2d7 Dockerfile: VDI support (commented out, documented)
- Run example with Docker socket mount and group-add for VDI
- Commented-out [vdi] section in default config template
- vdi-homes directory and volume for persistent home dirs
- No changes to default behavior — VDI stays disabled unless configured
2026-04-05 08:29:21 +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