mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-27 20:29:10 +00:00
0f0b22c51a
* 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.
102 lines
6.2 KiB
Plaintext
102 lines
6.2 KiB
Plaintext
---
|
||
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; 2–64 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.
|