fix: harden input validation across all RPC endpoints (PENTEST-02)
Manual security audit of 130+ RPC endpoints. Critical fixes: - LND: validate pubkey (66-char hex), Bitcoin addresses, channel points, amount bounds, payment request format, memo length, peer address - Package: validate_app_id on start/stop/restart/bundled-app handlers, validate volume host paths (must be under /var/lib/archipelago/), validate Docker image in bundled-app-start - Container: validate_app_id on all 6 handlers, canonicalize manifest paths - Network: path traversal prevention in connection request deletion - Backup: backup ID validation in delete handler - Webhooks: URL scheme validation, SSRF prevention for private IPs - Security: validate app_id in secret rotation - Interfaces: WiFi password length/null validation, strict IP/gateway/DNS parsing for static ethernet config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
daa33d098b
commit
89acc3ed5c
@@ -0,0 +1,68 @@
|
||||
use super::RpcHandler;
|
||||
use super::package::validate_app_id;
|
||||
use anyhow::Result;
|
||||
|
||||
impl RpcHandler {
|
||||
pub(super) async fn handle_security_rotate_secrets(
|
||||
&self,
|
||||
params: &serde_json::Value,
|
||||
) -> Result<serde_json::Value> {
|
||||
let app_id = params
|
||||
.get("app_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing app_id"))?;
|
||||
validate_app_id(app_id)?;
|
||||
|
||||
let secrets_dir = self.config.data_dir.join("secrets");
|
||||
let encryption_key = self.get_secrets_key();
|
||||
let mgr =
|
||||
archipelago_security::SecretsManager::new(secrets_dir, encryption_key)?;
|
||||
|
||||
let secret_ids = mgr.list_secrets(app_id).await?;
|
||||
let mut rotated = Vec::new();
|
||||
|
||||
for secret_id in &secret_ids {
|
||||
mgr.rotate_secret(app_id, secret_id).await?;
|
||||
rotated.push(secret_id.clone());
|
||||
}
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"app_id": app_id,
|
||||
"rotated_count": rotated.len(),
|
||||
"rotated_ids": rotated,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) async fn handle_security_list_expiring(
|
||||
&self,
|
||||
params: &serde_json::Value,
|
||||
) -> Result<serde_json::Value> {
|
||||
let max_age_days = params
|
||||
.get("max_age_days")
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(90);
|
||||
|
||||
let secrets_dir = self.config.data_dir.join("secrets");
|
||||
let encryption_key = self.get_secrets_key();
|
||||
let mgr =
|
||||
archipelago_security::SecretsManager::new(secrets_dir, encryption_key)?;
|
||||
|
||||
let expiring = mgr.list_expiring(max_age_days).await?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"max_age_days": max_age_days,
|
||||
"expiring_count": expiring.len(),
|
||||
"secrets": expiring,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Derive a 32-byte encryption key for secrets.
|
||||
/// Uses a fixed derivation from the data directory path as a stable key.
|
||||
fn get_secrets_key(&self) -> Vec<u8> {
|
||||
use sha2::{Digest, Sha256};
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"archipelago-secrets-v1-");
|
||||
hasher.update(self.config.data_dir.to_string_lossy().as_bytes());
|
||||
hasher.finalize().to_vec()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user