mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-11 19:26:56 +00:00
fix(fleet-secrets): restrict bundle management to admin hub sessions (#1274)
* fix(fleet-secrets): restrict bundle management to admin hub sessions Fleet Secrets exposes decrypted environment-variable values and writes credentials across the fleet, so every route now requires an admin role, runs only on the instance you are signed into, and rejects long-lived API tokens: - Add an admin-role check to all secrets routes; the frontend Secrets tab renders only for admin users so the affordance matches the backend gate. - Add /api/secrets/ to the hub-only path list so a request carrying a remote node id cannot be proxied to read another node's decrypted values. - Reject API tokens on every secrets route (browser admin sessions only), matching how registry credentials are handled. Also adds lifecycle and developer-mode diagnostic logging (never the secret values) and tests covering the admin boundary on every endpoint, API-token rejection, hub-only enforcement, and diagnostic gating. * fix(fleet-secrets): require a signed-in user session for all secrets routes The earlier API-token rejection only blocked opaque API tokens. node_proxy and pilot_tunnel JWTs are mapped to an admin role by the auth middleware without an API-token scope, so they still passed the admin gate and could read decrypted bundles via GET /api/secrets/:id. Replace the API-token check with requireUserSession, which rejects API tokens and node_proxy / pilot_tunnel machine credentials (userId 0) on every secrets route, returning SESSION_REQUIRED. The admin role is still enforced after. Tests now assert SESSION_REQUIRED for a full-admin API token across all nine routes and for node_proxy and pilot_tunnel JWTs. * test(fleet-secrets): mint the rejection-test token via the real endpoint The machine-credential test reconstructed an API token by sha256-hashing a raw key inline. That duplicated a hashing sink that CodeQL's js/insufficient-password-hash query flags (a false positive for a 256-bit random token, but a new occurrence in the diff). Create the token through POST /api/api-tokens instead, so the hashing stays in the production path and the test carries none of its own. Behavior and coverage are unchanged.
This commit is contained in:
@@ -32,9 +32,10 @@ export function isProxyExemptPath(path: string): boolean {
|
||||
// nodeId resolves to a remote node so a script/curl call cannot trick the proxy
|
||||
// into forwarding the request across a node boundary. This matters for the logs
|
||||
// feed (its admin gate lives in the local route handler, which the proxy would
|
||||
// skip when forwarding a remote nodeId) and for registries (a proxied registry
|
||||
// request would otherwise carry a plaintext secret to, or read stored
|
||||
// credentials from, the remote).
|
||||
// skip when forwarding a remote nodeId) and for registries and Fleet Secrets (a
|
||||
// proxied request would otherwise carry a plaintext secret to, or read stored
|
||||
// secret values from, the remote; the secrets routes' admin gate also lives in
|
||||
// the local route handler, which the proxy would skip).
|
||||
//
|
||||
// Entries are stored with a trailing slash; the matcher accepts the exact
|
||||
// collection path (without the trailing slash) AND any sub-path under it,
|
||||
@@ -54,6 +55,7 @@ export const HUB_ONLY_PREFIXES: readonly string[] = [
|
||||
'/api/logs/global/',
|
||||
'/api/system/log-stream-metrics/',
|
||||
'/api/registries/',
|
||||
'/api/secrets/',
|
||||
];
|
||||
|
||||
/** Returns true when the path is hub-only and must not be proxied to a remote node. */
|
||||
|
||||
Reference in New Issue
Block a user