mirror of
https://github.com/sol1/rustguac.git
synced 2026-09-10 09:35:45 +00:00
docs: document multi-Vault backends, per-credential scope, My Credentials UX
- configuration.md: [vault_shared]/[vault_local] backends, the DR rationale, user_credentials_default_scope, and the VAULT_SHARED/LOCAL_SECRET_ID env vars. - credential-variables.md: Credentials nav link, scroll/filter/collapsible editor, resumable focused prompt, needed-pill + banner, and the shared/local per-credential scope with its outage trade-off. - migration.md: vault-migrate runbook (dry-run, copy, cutover ordering). - CLAUDE.md: multi-Vault backend summary under the Vault section.
This commit is contained in:
@@ -59,6 +59,21 @@ 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":"..."}`
|
||||
- `<base_path>/users/<sanitized_email>` — per-user credential variables
|
||||
|
||||
#### Multiple Vault backends (DR)
|
||||
|
||||
Optional `[vault_shared]` / `[vault_local]` blocks (same keys as `[vault]`) give
|
||||
the `shared` / `instance` scopes their own Vault so one being down can't take the
|
||||
other with it. A bare `[vault]` is unchanged (shared+local both alias it). Secret
|
||||
IDs: `VAULT_SECRET_ID`, `VAULT_SHARED_SECRET_ID`, `VAULT_LOCAL_SECRET_ID`. Each
|
||||
backend connects/retries/renews independently; a down backend greys that scope in
|
||||
the UI. Per-credential scope: a credential variable can be stored shared or local
|
||||
(location = truth), toggled per-row in My Credentials (hidden with a single
|
||||
Vault); `user_credentials_default_scope` (default `local`) seeds new ones. Split
|
||||
an existing single-Vault deployment with `rustguac vault-migrate` (copy subtree +
|
||||
.config, then add the block + restart — routing is single-source, no read
|
||||
fallback). Implemented on branch `feature/multi-vault-dr` (see project memory).
|
||||
|
||||
### OIDC
|
||||
|
||||
|
||||
+46
-1
@@ -134,6 +134,49 @@ Enables the Vault-backed connections. Requires `VAULT_SECRET_ID` environment var
|
||||
| `client_cert` | — | Path to client certificate (PEM) for mTLS |
|
||||
| `client_key` | — | Path to client private key (PEM) for mTLS (required if `client_cert` is set) |
|
||||
|
||||
### Multiple Vault backends (disaster recovery)
|
||||
|
||||
By default the single `[vault]` serves both the shared and instance (local)
|
||||
address-book scopes. For DR across a fleet you can give each scope its own Vault
|
||||
so that one being unreachable cannot take the other down with it. Add either or
|
||||
both of the optional blocks below; each takes the same keys as `[vault]`.
|
||||
|
||||
| Block | Serves | Secret ID env var |
|
||||
|-------|--------|-------------------|
|
||||
| `[vault]` | Default/fallback for any scope without a dedicated backend; also the home of the LUKS key | `VAULT_SECRET_ID` |
|
||||
| `[vault_shared]` | The `shared` scope | `VAULT_SHARED_SECRET_ID` |
|
||||
| `[vault_local]` | The `instance` (local) scope | `VAULT_LOCAL_SECRET_ID` |
|
||||
|
||||
A bare `[vault]` with no overrides behaves exactly as a single-Vault deployment,
|
||||
so nothing changes for existing installs. Each backend connects, retries, and
|
||||
renews its token independently. If a dedicated backend is unreachable, that
|
||||
scope is shown as temporarily unavailable in the Connections tree while the
|
||||
other scopes keep working.
|
||||
|
||||
When `[vault_local]` is used, set its `instance_name` to the value the data was
|
||||
originally stored under, so the `instance/<name>/` paths line up. Splitting an
|
||||
existing single-Vault deployment is a one-time copy with the `vault-migrate`
|
||||
subcommand (see the [migration guide](migration.md)).
|
||||
|
||||
```toml
|
||||
# Primary/local Vault (always reachable on this instance)
|
||||
[vault]
|
||||
addr = "https://127.0.0.1:8200"
|
||||
role_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
instance_name = "dc1"
|
||||
|
||||
# Optional central Vault shared across the fleet
|
||||
[vault_shared]
|
||||
addr = "https://vault-central.example.com:8200"
|
||||
role_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
|
||||
```
|
||||
|
||||
Related top-level setting:
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `user_credentials_default_scope` | `local` | Where a new per-user credential variable is stored when more than one backend is configured: `local` (stays on this instance, survives a central outage) or `shared` (propagates fleet-wide). Ignored with a single Vault. |
|
||||
|
||||
## `[drive]` section
|
||||
|
||||
Enables file transfer for RDP (drive redirection) and SSH (SFTP).
|
||||
@@ -327,7 +370,9 @@ home_base = "/vdi-homes"
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `OIDC_CLIENT_SECRET` | Override OIDC client secret from config file |
|
||||
| `VAULT_SECRET_ID` | Vault AppRole secret ID |
|
||||
| `VAULT_SECRET_ID` | Vault AppRole secret ID for `[vault]` |
|
||||
| `VAULT_SHARED_SECRET_ID` | Vault AppRole secret ID for `[vault_shared]` (only if configured) |
|
||||
| `VAULT_LOCAL_SECRET_ID` | Vault AppRole secret ID for `[vault_local]` (only if configured) |
|
||||
| `RUST_LOG` | Log level (e.g., `info`, `debug`, `rustguac=debug`) |
|
||||
|
||||
### Setting environment variables for systemd
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Credential Variables
|
||||
|
||||
Credential variables let connections entries reference shared credentials by name instead of storing passwords directly. Users maintain their own credential values in Vault via the **My Credentials** dialog (gear menu). When a session launches, rustguac substitutes the variables from the user's saved values.
|
||||
Credential variables let connections entries reference shared credentials by name instead of storing passwords directly. Users maintain their own credential values in Vault via the **My Credentials** dialog (the **Credentials** link in the top navigation, or the gear menu). When a session launches, rustguac substitutes the variables from the user's saved values.
|
||||
|
||||
This gives a similar experience to LDAP credential passthrough in Apache Guacamole — users log in once and sessions just work — without rustguac needing to bind to LDAP. Credentials stay in Vault, never on disk or in the browser.
|
||||
|
||||
@@ -53,19 +53,48 @@ VDI entries that auto-derive credentials (the default for images that honour `VD
|
||||
|
||||
## My Credentials dialog
|
||||
|
||||
Access via the gear icon in the top-right corner of the connections page. The dialog:
|
||||
Open it from the **Credentials** link in the top navigation bar, or from the
|
||||
gear menu. The dialog:
|
||||
|
||||
- Shows all credential variables used across entries the user has access to
|
||||
- Groups variables by domain prefix
|
||||
- Groups variables by domain prefix, with each group collapsible
|
||||
- Provides a filter box (shown once the list is long) to find a variable by name or domain
|
||||
- Indicates how many entries use each variable
|
||||
- Masks password and key fields (saved values are not shown, but a placeholder confirms they exist)
|
||||
- Partial saves work — fill in what you have now, come back later for the rest
|
||||
- Scrolls its body while the title and the Save/Close buttons stay pinned, so it stays usable with many variables
|
||||
- Supports partial saves: fill in what you have now, come back later for the rest
|
||||
|
||||
### Finding what needs setting up
|
||||
|
||||
The Connections page surfaces missing credentials before a connection fails:
|
||||
|
||||
- A **"credentials needed"** pill marks entries that reference a variable you have not set yet.
|
||||
- A dismissible banner reports how many credentials are still to set up. The dismissal is remembered across refreshes and only reappears if a genuinely new unset credential shows up.
|
||||
|
||||
## Graceful degradation
|
||||
|
||||
- **All variables set** — session launches immediately, no prompting
|
||||
- **Some missing** — credential prompt appears with known values pre-filled; user only needs to fill gaps
|
||||
- **None set** — full credential prompt (same as entries without variables)
|
||||
- **All variables set:** the session launches immediately, no prompting.
|
||||
- **Some missing:** the connect opens My Credentials focused on just the missing variables, and the connection resumes automatically once you save them.
|
||||
- **None set:** full credential prompt (same as entries without variables).
|
||||
|
||||
## Shared and local credentials (multiple Vaults)
|
||||
|
||||
When more than one Vault backend is configured (see
|
||||
[Multiple Vault backends](configuration.md)), each credential can be stored in
|
||||
the **shared** Vault (propagates to every site) or kept **local** to this
|
||||
instance. My Credentials shows a **"Shared across sites"** checkbox per
|
||||
credential; the default for a new credential comes from
|
||||
`user_credentials_default_scope`. Reads merge both backends (a local value wins
|
||||
over a shared one of the same name), and toggling a credential moves it between
|
||||
the two.
|
||||
|
||||
With a single Vault there is only one store, so the checkbox is hidden and every
|
||||
credential is simply stored there.
|
||||
|
||||
Trade-off to be aware of: a credential kept in the shared Vault will not resolve
|
||||
while that Vault is unreachable, so a connection that references it fails even if
|
||||
the target and the local Vault are up. Keep credentials a site must never lose
|
||||
(for example break-glass logins) **local**.
|
||||
|
||||
## Vault storage
|
||||
|
||||
@@ -75,7 +104,9 @@ User credentials are stored in Vault KV v2 at:
|
||||
<base_path>/users/<sanitized_email>
|
||||
```
|
||||
|
||||
Each user gets a single Vault secret containing all their credential key-value pairs. Variable names are the keys, plaintext values are the values. The Vault policy must allow read/write to this path for authenticated users.
|
||||
Each user gets a single Vault secret (per backend) containing their credential key-value pairs. Variable names are the keys, plaintext values are the values. The Vault policy must allow read/write to this path for authenticated users.
|
||||
|
||||
With multiple Vault backends configured, a user's credentials are split by scope: local credentials live at this path in the local backend, shared credentials at the same path in the shared backend. The `users/*` policy is needed on whichever backends hold credentials.
|
||||
|
||||
### Required Vault policy
|
||||
|
||||
|
||||
@@ -125,3 +125,56 @@ Once imported, connections appear in the connections UI. You can:
|
||||
- The import is additive: existing entries in the target folder are not deleted or overwritten. If you re-run the import, entries with the same name will be updated.
|
||||
- Guacamole user/group permissions are not imported. Use rustguac's OIDC group mappings and folder `allowed_groups` instead.
|
||||
- Credentials (passwords, private keys) are imported into Vault where they are stored encrypted at rest and never touch disk.
|
||||
|
||||
# Splitting to multiple Vaults (disaster recovery)
|
||||
|
||||
If you already run a single Vault serving both the `shared` and `instance`
|
||||
scopes and want to move a scope onto a dedicated Vault (see
|
||||
[Multiple Vault backends](configuration.md)), the `vault-migrate` subcommand
|
||||
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.
|
||||
|
||||
## Step 1: Preview
|
||||
|
||||
Configure the new backend block (e.g. `[vault_shared]`) and its
|
||||
`VAULT_SHARED_SECRET_ID`, then dry-run the copy:
|
||||
|
||||
```bash
|
||||
VAULT_SECRET_ID=... VAULT_SHARED_SECRET_ID=... \
|
||||
rustguac --config /opt/rustguac/config.toml \
|
||||
vault-migrate --scope shared --from vault --to vault_shared --dry-run
|
||||
```
|
||||
|
||||
## Step 2: Copy
|
||||
|
||||
```bash
|
||||
VAULT_SECRET_ID=... VAULT_SHARED_SECRET_ID=... \
|
||||
rustguac --config /opt/rustguac/config.toml \
|
||||
vault-migrate --scope shared --from vault --to vault_shared
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--scope` | (required) | `shared` or `instance` |
|
||||
| `--from` / `--to` | (required) | Backend names: `vault`, `vault_shared`, or `vault_local` |
|
||||
| `--dry-run` | off | Preview without writing to the destination |
|
||||
| `--overwrite` | off | Overwrite entries that already exist at the destination (default: skip existing) |
|
||||
| `--users` | off | Also copy every per-user credential secret (`users/*`). This makes those credentials shared; normally you toggle per-credential in My Credentials instead. |
|
||||
|
||||
## Step 3: Cut over
|
||||
|
||||
Routing is deterministic and single-source: once `[vault_shared]` is configured,
|
||||
the `shared` scope reads only from it, with no fall-back to `[vault]`. So the
|
||||
order matters:
|
||||
|
||||
1. Copy the subtree first (Step 2).
|
||||
2. Then add the `[vault_shared]` block and restart rustguac.
|
||||
|
||||
Doing it the other way round makes shared connections briefly disappear (the
|
||||
data is safe in the old Vault, just not being read). Entries and folders are
|
||||
single-source; only per-user credentials merge across backends, so those never
|
||||
have a gap.
|
||||
|
||||
Reference in New Issue
Block a user