feat(secrets): scoped shared secrets — backend + API (Phase 1)
Secrets defined once and applied to many workloads by scope (global or per-app), encrypted at rest and resolved into container env as a low-precedence default layer: global-shared < app-shared < image cfg.Env < workload_env. A workload with no applicable shared secrets is byte-identical to the prior workload_env-only behavior. - store: shared_secrets table + CRUD + ListApplicableSharedSecrets (enabled global + app, global-first), UNIQUE(scope,app_id,name). - plugin.ResolveSharedSecrets + integration into BuildWorkloadEnv (static/dockerfile) and image buildEnv; best-effort — a shared-secret store/decrypt error never fails a deploy, and values are never logged. - REST CRUD at /api/shared-secrets (reads authed, mutations AdminOnly); values encrypted at the boundary via crypto.Encrypt and never returned (only a has_value flag), mirroring workload_env. UNIQUE collisions 409. Compose is out of scope (YAML-defined env). Frontend rule UI is Phase 2. Reviewed: go + security APPROVE (0 CRITICAL/HIGH); two MEDIUMs fixed (translateSQLError -> 409, no driver-message leak). Deferred defense-in- depth: json:"-" on the model value + a description length cap.
This commit is contained in:
@@ -441,6 +441,16 @@ func (s *Server) Router() chi.Router {
|
||||
r.Delete("/metric-alert-rules/{id}", s.deleteMetricAlertRule)
|
||||
})
|
||||
|
||||
// Shared secrets (env vars shared across workloads by scope).
|
||||
r.Get("/shared-secrets", s.listSharedSecrets)
|
||||
r.Get("/shared-secrets/{id}", s.getSharedSecret)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(auth.AdminOnly)
|
||||
r.Post("/shared-secrets", s.createSharedSecret)
|
||||
r.Patch("/shared-secrets/{id}", s.updateSharedSecret)
|
||||
r.Delete("/shared-secrets/{id}", s.deleteSharedSecret)
|
||||
})
|
||||
|
||||
// System resources (read-only).
|
||||
r.Get("/system/stats", s.getSystemStats)
|
||||
r.Get("/system/stats/history", s.getSystemStatsHistory)
|
||||
|
||||
Reference in New Issue
Block a user