Files
sencho/docs/features/fleet-secrets.mdx
T
Anso 0f0b22c51a feat(fleet): Fleet Secrets tab with env-var bundles (v1 MVP) (#965)
* feat(fleet): add Fleet Secrets tab with versioned env-var bundles (Skipper+)

Centralized, encrypted-at-rest secret bundles that can be pushed to labeled
nodes' stacks. Each save bumps a monotonic version; each push records a
per-node-per-version row in `secret_pushes` plus an entry in `audit_log`.
Conflict detection shows added/changed/unchanged/removed (informational)
diffs before write. Overlay merge preserves keys missing from the bundle.

- Adds `secrets`, `secret_versions`, `secret_pushes` tables.
- New `SecretsService` reuses CryptoService for AES-256-GCM, NodeLabelService
  for selectors, and direct fetch + Bearer for outbound calls to remote nodes.
- New `secretsRouter` with 9 endpoints under `/api/secrets`, gated by
  `requirePaid`. Mounted after the auth gate.
- Audit summary patterns added for the new routes.
- New Fleet › Secrets tab with bundle list, editor sheet (key=value rows,
  versions tab), and push wizard (selector, target stack, env file picker,
  per-node diff preview, results pills).
- Documentation: docs/features/fleet-secrets.mdx + docs.json nav entry.
- 26 Vitest cases cover parser, encryption, versioning, push aggregation,
  tier gating.

* fix(fleet): use const for rawValue in env parser

ESLint prefer-const flagged the let declaration as a CI-blocking error;
the variable is never reassigned.
2026-05-07 06:03:39 -04:00

102 lines
6.2 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Fleet Secrets"
description: "Centralized, encrypted, versioned env-var bundles you can push to labeled nodes' stacks."
---
Fleet Secrets gives you one place to author the environment variables a stack needs, and one action to push them to every node that runs that stack. Bundles are encrypted at rest, every save bumps a version, and every push is recorded in the audit log with a per-node diff.
Fleet Secrets is available on **Skipper** and **Admiral** licenses. The tab is hidden on Community.
## What's in scope
| Today | Not yet |
|-------|---------|
| Bundles of `KEY=value` env pairs | Certificates, key files, credential JSON blobs |
| Push to nodes by label selector | Pin a bundle to specific node IDs |
| Versioning on every save with a change note | Scheduled or auto-rotation |
| Diff preview before each push | Multi-region or per-environment overlays |
If a feature is not in the table above, it's deferred to a later release. Fleet Secrets in 1.0 is intentionally focused on env-var drift across copies of the same stack.
## Create a bundle
1. Open **Fleet → Secrets**.
2. Click **New bundle**.
3. Give it a name (letters, digits, dot, dash, underscore; 264 chars) and an optional description.
4. Add `KEY=value` rows. Keys must match the env-var convention (`A-Z`, digits, underscore; cannot start with a digit). The eye icon toggles value visibility; the copy icon copies a single value to the clipboard.
5. Add a **Change note** (optional but recommended) explaining why this version exists.
6. Click **Save**. The bundle is now version `v1`.
Saving the bundle encrypts the payload with the instance's data key (AES-256-GCM via the same key that protects MFA secrets and registry credentials). The plaintext only ever exists in memory while you're editing.
## Edit and version
Editing a bundle creates a new version. Each version is immutable once saved. Open a bundle and switch to the **Versions** tab to see who saved each version and any change notes attached.
Saves are last-write-wins. There's no row-level lock; if two operators edit the same bundle concurrently, the later save creates the higher version number and the earlier save remains visible in history.
## Push to nodes
A push reads the bundle's current version, computes the diff against each target's existing env file, and writes the merged result.
1. From the bundle row, click the **Send** action to open the push wizard.
2. **Target tab:**
- Pick one or more node labels.
- Choose **any** (a node matches if it has any of the selected labels) or **all** (a node must have all selected labels).
- Enter the **Stack name** that the bundle should be applied to. The stack must exist on each target node by that name.
- Pick the **Env file** to write. The dropdown lists every file declared by the stack's compose on a representative target node — the canonical `.env` plus any files referenced via `env_file:` directives. Per-node compose files can differ; nodes that do not declare the chosen file are reported as failed for that push.
3. Click **Preview**.
4. **Preview tab:** the table shows one row per matched node. Each row reports a quick header (`+added · ~changed · ·unchanged`) and an expander with the per-key diff. Drift — keys present on the target that are absent from the bundle — is shown as informational `removed` rows. The push will not delete those keys.
5. Click **Push to N nodes**.
6. **Results tab:** per-node status pills (`ok`, `failed`, `skipped`). Hover a failure to see the error.
Pushes are sequential per bundle to make audit ordering predictable. Only one push for a given bundle can run at a time; a second concurrent push attempt returns `409 Conflict` until the first finishes.
## Merge semantics: overlay
Fleet Secrets uses **overlay** merge:
- A key in the bundle that is missing from the target is **added**.
- A key in both with a different value is **changed** to the bundle's value.
- A key in both with the same value is **unchanged**.
- A key on the target that is **not in the bundle** is preserved on the target. The diff shows it under `removed (drift)` so you can see the divergence, but the push leaves it alone.
This is deliberately conservative for a v1 push action. If a key truly needs to be removed from a target's env file, do it in the stack editor on that node, then push the bundle to bring the rest of the keys in line.
## Audit trail
Every mutating action shows up in **Audit Log**:
| Action | Audit summary |
|--------|---------------|
| Create bundle | `Created secret` |
| Update bundle (new version) | `Updated secret: <id>` |
| Delete bundle | `Deleted secret: <id>` |
| Import env from a stack | `Imported env into secret: <id>` |
| Preview a push | `Previewed secret push: <id>` |
| Execute a push | `Pushed secret: <id>` |
The execute-push action also writes one row per target node in `secret_pushes`, capturing the bundle id, version, push id (a UUID grouping all rows from the same push), node id, stack name, env file, status, error message, and per-node `added` / `changed` / `unchanged` counts.
## Troubleshooting
### "env file '<name>' not found on node <X>"
The node's compose file for that stack does not declare the env file you asked Fleet Secrets to write to. Either pick a different env file in the wizard's **Env file** dropdown, or update that node's compose to reference the file via `env_file:`.
### "stack not found"
The named stack does not exist on that node. Fleet Secrets pushes to existing stacks only — it does not create stack directories.
### A push partially succeeded
Per-node failures are independent. The bundle and other targets are unaffected; only the failing node missed the push. Fix the underlying cause (file declared in compose, stack created, node reachable) and re-run the push. Idempotent overlay means re-pushing is safe.
### "A push for this secret is already running"
Only one push per bundle runs at a time. Wait for the previous push to finish, then retry.
### Values look wrong on the target
Open the bundle and switch to **Versions** — confirm the version you intended to push is the current one. Open the wizard, run **Preview** without pushing, and read the diff. The preview is a faithful representation of what the next push will write.