feat: add container security hardening and Fedimint setup wizard
Add --cap-drop=ALL, --security-opt=no-new-privileges:true to all non-privileged containers. Per-app capability grants for apps needing CHOWN/SETUID/SETGID. Read-only root filesystem with tmpfs for compatible apps (searxng, grafana, uptime-kuma, filebrowser, photoprism, vaultwarden). Add Fedimint "Create a Community" goal with 4-step wizard. Fix deploy script cp -rf for audio directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,27 @@ impl RpcHandler {
|
||||
run_args.push("--network=archy-net");
|
||||
}
|
||||
|
||||
// Security hardening (skip for privileged containers like Tailscale)
|
||||
let security_caps: Vec<String> = if !is_tailscale {
|
||||
get_app_capabilities(package_id)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let readonly_compatible = !is_tailscale && is_readonly_compatible(package_id);
|
||||
|
||||
if !is_tailscale {
|
||||
run_args.push("--cap-drop=ALL");
|
||||
run_args.push("--security-opt=no-new-privileges:true");
|
||||
for cap in &security_caps {
|
||||
run_args.push(cap);
|
||||
}
|
||||
if readonly_compatible {
|
||||
run_args.push("--read-only");
|
||||
run_args.push("--tmpfs=/tmp:rw,noexec,nosuid,size=256m");
|
||||
run_args.push("--tmpfs=/run:rw,noexec,nosuid,size=64m");
|
||||
}
|
||||
}
|
||||
|
||||
// Create data directories if they don't exist
|
||||
for volume in &volumes {
|
||||
if let Some(host_path) = volume.split(':').next() {
|
||||
@@ -776,6 +797,51 @@ fn is_valid_docker_image(image: &str) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Per-app Linux capabilities needed beyond the default cap-drop=ALL.
|
||||
/// Most apps need CHOWN/SETUID/SETGID for internal user switching.
|
||||
fn get_app_capabilities(app_id: &str) -> Vec<String> {
|
||||
match app_id {
|
||||
// Apps that need user switching and file ownership changes
|
||||
"nextcloud" | "homeassistant" | "home-assistant" | "btcpay-server" | "btcpayserver"
|
||||
| "jellyfin" | "onlyoffice" | "onlyoffice-documentserver" | "portainer" => vec![
|
||||
"--cap-add=CHOWN".to_string(),
|
||||
"--cap-add=SETUID".to_string(),
|
||||
"--cap-add=SETGID".to_string(),
|
||||
"--cap-add=DAC_OVERRIDE".to_string(),
|
||||
],
|
||||
// Nginx Proxy Manager needs to bind low ports
|
||||
"nginx-proxy-manager" => vec![
|
||||
"--cap-add=CHOWN".to_string(),
|
||||
"--cap-add=SETUID".to_string(),
|
||||
"--cap-add=SETGID".to_string(),
|
||||
"--cap-add=NET_BIND_SERVICE".to_string(),
|
||||
],
|
||||
// Bitcoin and Lightning need file ownership ops
|
||||
"bitcoin" | "bitcoin-core" | "bitcoin-knots" | "lnd" | "fedimint" => vec![
|
||||
"--cap-add=CHOWN".to_string(),
|
||||
"--cap-add=SETUID".to_string(),
|
||||
"--cap-add=SETGID".to_string(),
|
||||
],
|
||||
// Grafana runs as specific UID (472)
|
||||
"grafana" => vec![
|
||||
"--cap-add=CHOWN".to_string(),
|
||||
"--cap-add=SETUID".to_string(),
|
||||
"--cap-add=SETGID".to_string(),
|
||||
],
|
||||
// Minimal apps (searxng, filebrowser, uptime-kuma, etc.) need no extra caps
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Apps safe to run with --read-only root filesystem.
|
||||
/// These work correctly with volume mounts + tmpfs for /tmp and /run.
|
||||
fn is_readonly_compatible(app_id: &str) -> bool {
|
||||
matches!(
|
||||
app_id,
|
||||
"searxng" | "grafana" | "uptime-kuma" | "filebrowser" | "photoprism" | "vaultwarden"
|
||||
)
|
||||
}
|
||||
|
||||
/// Get app-specific configuration
|
||||
/// Returns: (ports, volumes, env_vars, custom_command, custom_args)
|
||||
fn get_app_config(
|
||||
|
||||
Reference in New Issue
Block a user