feat(v1.1.7-secrets): secrets SDK + table + admin API + dashboard
Encrypted per-app secrets, reachable from scripts as
secrets::{get,set,delete,list}(name) and managed from the dashboard
Secrets tab. Values are AES-256-GCM-sealed with the process master key
(picloud_shared::crypto) before they touch Postgres; the repo only ever
sees ciphertext + nonce. JSON round-trip preserves Rhai types.
- migration 0023_secrets.sql (PRIMARY KEY (app_id, name)).
- SecretsService trait (picloud-shared) + SecretsServiceImpl + repo
(manager-core), wired into the Services bundle and Rhai engine.
- Capability::AppSecretsRead/Write (→ script:read / script:write); no
new Scope variants (seven-scope commitment).
- Admin API GET/POST/DELETE /apps/{id}/secrets (list returns names +
updated_at, never values).
- build_app now takes a MasterKey, sourced from PICLOUD_SECRET_KEY in
main.rs; test callers pass a fixed test key.
- 64 KB value cap (PICLOUD_SECRET_MAX_VALUE_BYTES); no ServiceEvent
emission (secret writes don't fire triggers, by design).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,11 @@ async fn run_server() -> anyhow::Result<()> {
|
||||
let database_url =
|
||||
std::env::var("DATABASE_URL").map_err(|_| anyhow::anyhow!("DATABASE_URL is required"))?;
|
||||
|
||||
// Source the process master key BEFORE doing any work — an unset or
|
||||
// malformed PICLOUD_SECRET_KEY is fatal (v1.1.7). The only escape
|
||||
// hatch is PICLOUD_DEV_MODE=true, which logs a prominent warning.
|
||||
let master_key = picloud_shared::MasterKey::from_env()?;
|
||||
|
||||
let pool = init_db(&database_url).await?;
|
||||
migrations::run(&pool).await?;
|
||||
tracing::info!("migrations applied");
|
||||
@@ -69,7 +74,7 @@ async fn run_server() -> anyhow::Result<()> {
|
||||
// so a delayed sweep can't extend session lifetimes.
|
||||
spawn_session_pruner(auth.sessions.clone());
|
||||
|
||||
let app = build_app(pool, auth).await?;
|
||||
let app = build_app(pool, auth, master_key).await?;
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||
tracing::info!(%addr, "picloud all-in-one listening");
|
||||
|
||||
Reference in New Issue
Block a user