Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Vault KV v2 path structure:
- `<base_path>/shared/<folder>/<entry>` — shared across all instances
- `<base_path>/instance/<name>/<folder>/<entry>` — instance-specific
- `<folder>/.config` — folder metadata: `{"allowed_groups":["group1"], "description":"..."}`
- `<folder>/.defaults` — default credentials per session type, lent to entries without their own; subfolders inherit them up to (not past) a folder with `inherit_from_parent: false`
- `<base_path>/users/<sanitized_email>` — per-user credential variables

#### Multiple Vault backends (DR)
Expand Down
36 changes: 35 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Quick-connect endpoint for external integrations (e.g., NetBox Custom Links). Cr

When `scope`, `folder`, and `entry` are all provided, the endpoint connects via the connections (credentials from Vault). Otherwise it creates an ad-hoc session. No credentials are passed in the URL for ad-hoc mode — if the target requires authentication, the user will see guacd's login prompt.

If the connections entry has `prompt_credentials: true` or has no stored password/key, the endpoint returns an inline credential form instead of creating the session immediately. The user enters credentials, which are POSTed to the connect endpoint and used for that session only (never stored).
If the connections entry has `prompt_credentials: true` or has no stored password/key, the endpoint returns an inline credential form instead of creating the session immediately. The user enters credentials, which are POSTed to the connect endpoint and used for that session only (never stored). An entry without its own secret that is covered by [folder default credentials](credential-variables.md#folder-default-credentials) connects with those, exactly as it does from the Connections page.

See [NetBox Integration](netbox.md) for usage with NetBox Custom Links.

Expand Down Expand Up @@ -597,6 +597,40 @@ For `spice` and `proxmox` entries, the `spice_*` and `proxmox_*` fields listed u

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.

### `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, up to but not past a subfolder with `inherit_from_parent: false`) 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. See [Folder default credentials](credential-variables.md#folder-default-credentials) for the storage layout, the inheritance rule and the note on web entries.

```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`.


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

Delete a connection entry. Returns `404` if there is no such entry.
Expand Down
42 changes: 42 additions & 0 deletions docs/credential-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,45 @@ path "secret/metadata/rustguac/users/*" {
| `GET` | `/api/me/credentials` | operator+ | List own saved variables (passwords masked) |
| `PUT` | `/api/me/credentials` | operator+ | Save/update own variables |
| `GET` | `/api/credential-variables` | operator+ | List all variables used across accessible entries |

## Folder default credentials

A folder can lend credentials to entries that have none of their own, per session type. An entry created without secrets (a NetBox webhook, a bulk import) is then connectable the moment it exists, with no second pass to fill it in. Admins set them from the folder dialog in Connections ("Default credentials") or with the `/api/addressbook/folders/:scope/:folder/defaults` endpoints (see [API reference](api.md)).

### Where they live

Defaults are stored in Vault at `<folder>/.defaults`, beside the folder's `.config`:

```
<base_path>/shared/<folder>/.config access settings
<base_path>/shared/<folder>/.defaults default credentials, keyed by session type
```

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

Each type can carry `username`, `password`, `private_key` and `domain`. A type with no password or private key lends nothing (a username alone cannot authenticate). The existing connections Vault policy covers this path; no policy change is needed. `.defaults` is a reserved name, like `.config`: it is never listed as an entry and an entry cannot be called that.

### How they apply

At connect time, an entry with no password or private key of its own, and not set to `prompt_credentials`, takes the defaults for its session type. The entry's own `username` and `domain` win when set; otherwise the defaults' values fill them. An entry with its own secret is never touched, and an entry set to prompt still prompts. This happens on both connect paths, the Connections page (`POST .../entries/:entry/connect`) and quick-connect links (`GET /api/connect?scope=...&folder=...&entry=...`), through the same resolver, so an entry that works from the page works from a link.

Entries covered by defaults list with `has_credentials: true` and `inherited_credentials: true`, and show a `[folder]` marker in Connections.

### Inheritance and the `inherit_from_parent` boundary

A subfolder without its own `.defaults` uses its nearest ancestor's, per session type: the nearest folder on the path wins for each type, and a type it lacks comes from the next folder up.

The walk up the tree follows the same rule as access: it continues past a folder only while that folder's `.config` has `inherit_from_parent: true`. A folder with `inherit_from_parent: false` is isolated for access (see [Roles and access control](roles-and-access-control.md#inheritance)), and defaults set on its parent do not flow into it. Otherwise a parent's key would be usable in a subfolder shared with someone else through `allowed_users`. The isolated folder's own `.defaults` still apply to its entries and to subfolders that inherit from it. A folder with no `.config`, or one written before the flag existed, counts as not inheriting, exactly as the access check treats it.

### Migration between Vaults

`rustguac vault-migrate` copies each folder's `.defaults` alongside its `.config`, so a DR split keeps the defaults; the dry run lists them as `[defaults] <folder>`. Deleting a folder removes its `.defaults` with it.

### Web entries: the password is visible to the user

A **web** entry with a default password has that password typed into the page by the login autofill, the same as a web entry with its own stored password. The user can read it from the page (a show-password toggle, the browser's developer tools, a form that echoes it). Treat a web default as shared with everyone who can connect through that folder, and prefer per-entry or per-user credentials (`$variables`) where the password must stay secret. SSH, RDP and VNC defaults are passed to guacd and never reach the browser.
3 changes: 2 additions & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ scopes and want to move a scope onto a dedicated Vault (see
copies a scope's whole subtree between two configured backends. Because the
scope-to-path layout is identical in every backend, this is a same-identity
copy, not a rewrite: it moves the entries **and** each folder's access config
(`.config`), so `allowed_groups` and inheritance travel with them.
(`.config`) and default credentials (`.defaults`), so `allowed_groups`,
inheritance and the credentials entries borrow all travel with them.

## Step 1: Preview

Expand Down
2 changes: 2 additions & 0 deletions docs/roles-and-access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ Because folder settings live in Vault, they apply on every rustguac instance sha

A subfolder created with `inherit_from_parent: true` (the default for new subfolders) grants access to anyone who can access its parent. A subfolder with `inherit_from_parent: false` is gated solely by its own `allowed_groups` and `allowed_users`, independent of the parent.

The same flag bounds [folder default credentials](credential-variables.md#folder-default-credentials): defaults set on a parent do not flow into a subfolder with `inherit_from_parent: false`, so isolating a folder for access also keeps the parent's secrets out of it.

### Examples

A folder with `allowed_groups: ["engineering", "devops"]`:
Expand Down
Loading
Loading