Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
aae1163
csp: allow data: and blob: images so Safari and Firefox can draw
jseifeddine Oct 10, 2026
27f9b18
client: fill a small framebuffer shortfall instead of leaving a strip
jseifeddine Oct 10, 2026
7513c51
connections: fit the entries table on 1710px-wide displays
jseifeddine Oct 10, 2026
91639a9
connections: live refresh, folder default credentials, moving upserts
jseifeddine Oct 10, 2026
6f51cfb
connections: sticky self-scrolling sidebar, flush-left folder rows (#…
jseifeddine Oct 10, 2026
17cd3e5
connections: selection feedback and in-place sidebar updates (#254)
jseifeddine Oct 10, 2026
c9f3177
Merge branch 'ux/layout' into ux-enhancements
jseifeddine Oct 10, 2026
f3c5088
css: share the narrow-desktop page padding across pages
jseifeddine Oct 10, 2026
f205c72
Merge branch 'ux/selection' into ux-enhancements
jseifeddine Oct 10, 2026
d8da01c
connections: folder filter box, keyboard tree navigation and ARIA rol…
jseifeddine Oct 10, 2026
11464b8
Merge branch 'ux/filter' into ux-enhancements
jseifeddine Oct 10, 2026
9185874
pages: bake the caller's role into the served page, version the logo
jseifeddine Oct 10, 2026
a6dc146
pages: one navigation for every page, gated by role in CSS
jseifeddine Oct 10, 2026
77284b6
connections: app shell with independent panes, sidebar panel, tenant …
jseifeddine Oct 10, 2026
8d0a472
client: keep the fullscreen gesture press off the remote, release all…
jseifeddine Oct 11, 2026
7fb48ff
rdp: size a fullscreen-on-connect session to the screen from the firs…
jseifeddine Oct 11, 2026
2ed8015
connections: scroll only the entries pane on selection, clip the body
jseifeddine Oct 11, 2026
f0735c7
reports: show entry names as host over a tenant subline
jseifeddine Oct 11, 2026
ab5d699
reports, recordings: fit the tables on 1710px-wide displays
jseifeddine Oct 11, 2026
eed80bc
rdp: screen-size start only for RDP/VNC, and only until the first ful…
jseifeddine Oct 11, 2026
4db373a
client: hold the picture behind a gate until fullscreen on connect (#…
jseifeddine Oct 11, 2026
60e73c5
sessions, recordings: show the session type with the shared badge (#254)
jseifeddine Oct 11, 2026
24af7d9
pages: facet filters, sticky folder header, ad-hoc session modal, fol…
jseifeddine Oct 11, 2026
ca6c84b
folders: optional display name in the folder config (#254)
jseifeddine Oct 11, 2026
d927cf7
sessions, connections: tidy the ad-hoc and folder modals (#254)
jseifeddine Oct 11, 2026
647c75f
client: fill the viewport on connect, fullscreen on the first interac…
jseifeddine Oct 11, 2026
f55b98f
connections: move search to the brand row, engaged mode, tidy results…
jseifeddine Oct 11, 2026
c3394b0
client: go fullscreen before painting a fullscreen-on-connect session…
jseifeddine Oct 11, 2026
658b093
connections: search results as a floating overlay; left gutter; match…
jseifeddine Oct 11, 2026
a51cffb
ui: align the ad-hoc modal buttons and center section headings (#254)
jseifeddine Oct 11, 2026
bab083c
search: make the connection search global, on every page (#254)
jseifeddine Oct 11, 2026
6abfea1
search: raise the box above the overlay, fix the icon, cache the inde…
jseifeddine Oct 11, 2026
10060d0
connections: pad the content, fix the facet scrollbar, spinner on Con…
jseifeddine Oct 11, 2026
a4d9f80
ui: themed confirm/alert dialogs in place of the native ones (#254)
jseifeddine Oct 11, 2026
361ebf2
connections: pad the folder header to align with the table; search-in…
jseifeddine Oct 11, 2026
1ecec26
search: single shared component for every page, including connections…
jseifeddine Oct 11, 2026
952bd1f
search: escape the session type in the badge class (#254)
jseifeddine Oct 11, 2026
8ec0fbb
ui, search: audit hardening (#254)
jseifeddine Oct 11, 2026
381b41d
connections, search, client: toolbar buttons, robust search close, ze…
jseifeddine Oct 11, 2026
8b5a5f1
search: style the results table fully in the shared stylesheet (#254)
jseifeddine Oct 11, 2026
096b6b5
connect: true zero-click fullscreen via same-tab iframe (#257)
jseifeddine Oct 11, 2026
5f135c8
connect: fix framed session (black screen), reuse, sharp RDP fit, titles
jseifeddine Oct 11, 2026
868d6f2
session: only reuse a freshly-created Pending session (<15s)
jseifeddine Oct 11, 2026
5175f1b
sessions, reports: standardise the list toolbar across pages (#254)
jseifeddine Oct 11, 2026
77e8aad
connect: windowed ⧉ never fullscreens; framed reveal waits for the vi…
jseifeddine Oct 11, 2026
21ff3e0
connect: create the framed session at the measured fullscreen viewport
jseifeddine Oct 11, 2026
d6b9d8d
connect: RDP framed session sized from the measured viewport, mod-16 …
jseifeddine Oct 11, 2026
19bcc72
connect: size the ⧉ popup session from the popup's own measured viewport
jseifeddine Oct 11, 2026
9e93348
api: rustfmt the sizing test
jseifeddine Oct 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,54 @@ For `spice` and `proxmox` entries, the `spice_*` and `proxmox_*` fields listed u

### `PUT /api/addressbook/folders/:scope/:folder/entries/:entry` (admin)

Update a connection entry. Uses read-modify-write: reads existing entry from Vault, merges incoming fields on top. Credentials (`password`, `private_key`) that are omitted from the request are preserved from the existing entry. Jump host credentials are merged per-hop by index.
Update a connection entry, or create it if it does not exist. Uses read-modify-write: reads existing entry from Vault, merges incoming fields on top. Credentials (`password`, `private_key`) that are omitted from the request are preserved from the existing entry. Jump host credentials are merged per-hop by index.

**`?move=true`** makes this folder the entry's only home in the scope: after the write, same-named entries in every other folder of the scope (subfolders included) are deleted, each with its own `delete_entry` audit record. For upserts keyed on a stable name that can change folder, such as a NetBox webhook writing to the device's current tenant folder: when the tenant changes, the entry follows instead of being doubled. The response lists what was removed:

```json
{ "ok": true, "removed_from": ["mrgelato"] }
```

### `GET /api/addressbook/folders/:scope/:folder/defaults` (admin)

The folder's default credentials, secrets stripped. These are lent to entries in the folder (and in subfolders without their own, per session type) that have no password or private key of their own, so an entry created without secrets by a webhook or an import is connectable the moment it exists. An entry's own credentials always win, and an entry set to `prompt_credentials` still prompts.

```json
{
"defaults": {
"ssh": { "username": "root", "domain": null, "has_password": false, "has_private_key": true },
"web": { "username": null, "domain": null, "has_password": true, "has_private_key": false }
}
}
```

Entries covered by folder defaults list with `has_credentials: true` and `inherited_credentials: true`.

### `PUT /api/addressbook/folders/:scope/:folder/defaults` (admin)

Set default credentials per session type. Types not mentioned keep what is stored; `null` removes a type. Within a type an omitted field keeps the stored value and `""` clears it, the same rule entry PUTs use for secrets.

```json
{
"ssh": { "username": "root", "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n..." },
"web": { "password": "router-password" },
"rdp": null
}
```

Returns the same summary as `GET`. A type left with nothing in it is dropped; a folder left with no types loses its defaults entirely. Audited as `update_folder_defaults`.

### `DELETE /api/addressbook/folders/:scope/:folder/defaults` (admin)

Remove the folder's default credentials. Returns `204`.

### `GET /api/addressbook/version`

A counter rustguac bumps after every successful change made through its API to the address book (entries, folders, folder config and defaults, moves, copies, bulk operations) or to the caller's credential variables. Requires **operator**. It costs no Vault read, so clients can poll it often and reload only when it moves; the Connections page does so every 3 seconds. `changed_at` is unix milliseconds, `0` before the first change since start. The counter is per process: with several rustguac instances on one Vault, each sees only its own changes, so the page also refreshes unconditionally every two minutes.

```json
{ "version": 17, "changed_at": 1760155200000 }
```

### `DELETE /api/addressbook/folders/:scope/:folder/entries/:entry` (admin)

Expand Down
19 changes: 17 additions & 2 deletions docs/netbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ Use Event Rule **conditions** to sync only the devices you want. You can filter

2. **Create the Webhook**:
- Name: `rustguac-sync-create`
- URL: `https://console.example.com/api/addressbook/folders/shared/netbox-sync/entries`
- HTTP method: POST
- URL: `https://console.example.com/api/addressbook/folders/shared/netbox-sync/entries/{{ data.name | lower }}?move=true`
- HTTP method: PUT
- HTTP content type: `application/json`
- Additional headers:
```
Expand All @@ -161,6 +161,21 @@ Use Event Rule **conditions** to sync only the devices you want. You can filter

**Important:** The entry field is `type`, not `session_type` (it matches the Vault storage format). The hostname uses `.split('/')[0]` to strip the CIDR prefix from NetBox IP addresses (e.g. `10.0.0.1/24` → `10.0.0.1`). Avoid `regex_replace` and `cut` filters — they are not available in NetBox's Jinja2 environment.

`PUT` creates the entry on first sight and updates it afterwards, keeping any password or key already stored in Vault. `?move=true` matters when the folder is computed from the device, for example one folder per tenant (`.../folders/shared/{{ data.tenant.slug }}/entries/{{ data.name | lower }}?move=true`): when a device changes tenant, the entry follows it and the copy in the old folder is removed, instead of both existing. See [`PUT .../entries/:entry`](api.md#put-apiaddressbookfoldersscopefolderentriesentry-admin).

### Credentials for synced entries

NetBox never carries secrets, so a webhook creates entries without a password or key. Rather than a second pass that fills them in later, give the folder **default credentials**: a per-session-type username, password or private key that entries without their own borrow at connect time. Set them once on the folder (Connections, edit folder, Default credentials; or [`PUT .../defaults`](api.md#put-apiaddressbookfoldersscopefolderdefaults-admin)):

```bash
curl -X PUT https://console.example.com/api/addressbook/folders/shared/netbox-sync/defaults \
-H "Authorization: Bearer <admin-api-key>" \
-H "Content-Type: application/json" \
-d '{"ssh": {"username": "root", "private_key": "'"$(sed ':a;N;$!ba;s/\n/\\n/g' fleet-key)"'"}}'
```

A synced entry then shows **Connect**, not **Login...**, from the moment the webhook fires. An entry that is given credentials of its own keeps them; an entry with `"prompt_credentials": true` still prompts. The Connections page picks all of this up on its own within a few seconds (it polls [`GET /api/addressbook/version`](api.md#get-apiaddressbookversion)), so nobody has to reload.

### Create webhook: device deleted

1. **Create an Event Rule**:
Expand Down
33 changes: 33 additions & 0 deletions docs/reverse-proxies.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,36 @@ trusted_proxies = ["127.0.0.1/32"]

Without this setting, all requests from behind the proxy appear to come
from the proxy's own IP.

## Content-Security-Policy

rustguac sends its own `Content-Security-Policy` on every response. Leave
it alone: the Guacamole client needs `img-src 'self' data: blob:` because
browsers without WebCodecs `ImageDecoder` (Safari, Firefox) draw every
image tile from a `data:` URI. If your proxy sets a CSP of its own, keep
that directive, or SSH/RDP sessions render black in those browsers while
Chrome looks fine.

Releases up to v1.10.5 shipped a policy with no `img-src`, which is
exactly that failure (#249). If you can't upgrade, have the proxy replace
the header. The whole policy has to be repeated because the header is
replaced, not merged:

HAProxy (backend section):

```
http-response set-header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' wss: ws:; img-src 'self' data: blob:"
```

nginx:

```nginx
proxy_hide_header Content-Security-Policy;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' wss: ws:; img-src 'self' data: blob:" always;
```

Caddy:

```
header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' wss: ws:; img-src 'self' data: blob:"
```
Loading