Files
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

8.0 KiB

NetBox Integration

rustguac integrates with NetBox to provide one-click remote console access from device pages. No NetBox plugin is required — the integration uses NetBox's built-in Custom Links, Custom Fields, and Event Rules.

Note: NetBox Custom Links and Webhook body templates use Jinja2 template syntax. Filter arguments use parentheses — default('ssh') — not Django's colon syntax (default:'ssh'). Only standard Jinja2 filters are available (e.g. lower, default, split). Ansible filters like regex_replace and Django filters like cut are not available. Custom Links use object.cf.field_name for custom fields; Webhook body templates use data.custom_fields.field_name (the REST API serialization).

Custom Fields

Go to Customization > Custom Fields and create the following fields. Assign each to dcim > device (and virtualization > virtual machine if you use VMs).

Name Type Default Description
console_enabled Boolean false Opt-in: enables remote console links on the device page
console_mode Selection: addressbook, adhoc How to connect: via connections entry (Vault credentials) or ad-hoc (direct to IP)
remote_protocol Selection: ssh, rdp, vnc, web Protocol for ad-hoc connections (connections entries have their own)
remote_port Integer Port override for ad-hoc connections (leave blank for protocol default)

The console_enabled field is the master switch — no links appear until it's checked. The console_mode field controls which link is shown:

  • addressbook — connects via a Vault connections entry. Credentials are managed in Vault and never appear in the URL. Requires a matching entry name (lowercase device name). Minimum role: operator.
  • adhoc — connects directly to the device's primary IP. No stored credentials — the user sees guacd's login prompt. Minimum role: poweruser.

Create two Custom Links in Customization > Custom Links. Each link only renders when console_mode matches its mode, so only one appears per device.

Setting Value
Content Types dcim > device, virtualization > virtual machine
Name Console
Button class Green
New window Yes

Link text:

{% if object.primary_ip4 and object.cf.console_enabled and object.cf.console_mode == 'addressbook' %}Console{% endif %}

Link URL:

https://console.example.com/api/connect?scope=shared&folder=production&entry={{ object.name | lower }}

Replace production with your connections folder name. The entry name must match the lowercase device name in Vault.

Setting Value
Content Types dcim > device, virtualization > virtual machine
Name Quick SSH
Button class Blue (outline)
New window Yes

Link text:

{% if object.primary_ip4 and object.cf.console_enabled and object.cf.console_mode == 'adhoc' %}Quick SSH{% endif %}

Link URL:

https://console.example.com/api/connect?hostname={{ object.primary_ip4.address.ip }}&protocol={{ object.cf.remote_protocol | default('ssh') }}{% if object.cf.remote_port %}&port={{ object.cf.remote_port }}{% endif %}

Enabling on a device

  1. Edit the device in NetBox
  2. Check Console Enabled
  3. Set Console Mode to addressbook or adhoc
  4. (Ad-hoc only) Optionally set Remote Protocol and Remote Port
  5. Save — the appropriate button appears on the device page

Use NetBox's bulk edit to enable across multiple devices at once.

Role Requirements

Mode Minimum role Description
Connections operator Connects via Vault entry (credentials from Vault)
Ad-hoc poweruser Creates session directly to hostname

Webhook-Driven Connections Sync

Automatically sync NetBox devices to rustguac's Vault-backed connections using Event Rules and Webhooks. This keeps connections entries in sync with NetBox — when a device is created or updated, the corresponding entry is created in Vault.

Filtering: control what syncs

Use Event Rule conditions to sync only the devices you want. You can filter by any combination of:

By console_enabled field (recommended):

{
  "and": [
    {"attr": "status.value", "value": "active"},
    {"attr": "custom_fields.console_enabled", "value": true}
  ]
}

By tag:

{
  "and": [
    {"attr": "status.value", "value": "active"},
    {"attr": "tags.slug", "op": "contains", "value": "remote-console"}
  ]
}

By site:

{
  "and": [
    {"attr": "status.value", "value": "active"},
    {"attr": "site.slug", "value": "dc1"}
  ]
}

By device role:

{
  "and": [
    {"attr": "status.value", "value": "active"},
    {"attr": "role.slug", "value": "server"}
  ]
}

Create webhook: device created/updated

  1. Create an Event Rule (Operations > Event Rules):

    • Name: rustguac-sync-create
    • Content Types: dcim > device
    • Events: Object created, Object updated
    • Conditions: your filter (see above)
    • Action type: Webhook
  2. Create the Webhook:

    • Name: rustguac-sync-create

    • URL: https://console.example.com/api/addressbook/folders/shared/netbox-sync/entries

    • HTTP method: POST

    • HTTP content type: application/json

    • Additional headers:

      Authorization: Bearer <admin-api-key>
      
    • Body template:

      {
        "name": "{{ data.name | lower }}",
        "type": "{{ data.custom_fields.remote_protocol | default('ssh') }}",
        "hostname": "{{ data.primary_ip4.address.split('/')[0] }}",
        "port": {{ data.custom_fields.remote_port | default(22) }},
        "display_name": "{{ data.name }} ({{ data.site.name }})",
        "prompt_credentials": true
      }
      

      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/2410.0.0.1). Avoid regex_replace and cut filters — they are not available in NetBox's Jinja2 environment.

Create webhook: device deleted

  1. Create an Event Rule:

    • Name: rustguac-sync-delete
    • Content Types: dcim > device
    • Events: Object deleted
    • Action type: Webhook
  2. Create the Webhook:

    • Name: rustguac-sync-delete
    • URL: https://console.example.com/api/addressbook/folders/shared/netbox-sync/entries/{{ data.name | lower }}
    • HTTP method: DELETE
    • Additional headers:
      Authorization: Bearer <admin-api-key>
      

Folder setup

Before webhooks can create entries, create the target folder via the API:

curl -X POST https://console.example.com/api/addressbook/folders \
  -H "Authorization: Bearer <admin-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "shared",
    "name": "netbox-sync",
    "allowed_groups": ["network-ops", "sysadmins"],
    "description": "Auto-synced from NetBox"
  }'

Shared SSO

Both NetBox and rustguac support OIDC authentication. When configured with the same OIDC provider (Authentik, Keycloak, Okta, etc.), users authenticate once and get sessions in both applications. The Custom Link in NetBox opens rustguac, which recognises the existing SSO session — no second login prompt.

Example: Full Setup

  1. Configure rustguac with OIDC (see Integrations > OIDC)
  2. Configure NetBox with the same OIDC provider
  3. Create the four custom fields (console_enabled, console_mode, remote_protocol, remote_port)
  4. Create the two Custom Links (Console green, Quick SSH blue)
  5. On devices you want to enable: check console_enabled, set console_mode
  6. For connections mode: ensure matching entries exist in Vault (manually or via webhook sync)
  7. Users click the button on a device page and land in a session