refactor(security): tighten capability + TLS-bypass surface

Three small, focused tightenings:

- core/container/src/podman_client.rs: drop the legacy Hetzner
  23.182.128.160:3000 mirror from image_uses_insecure_registry().
  It was decommissioned in v1.7.x and is stripped from active
  registry config at load time; leaving it in the bypass list let
  a stale config still skip TLS. Replace the inline match with a
  named INSECURE_REGISTRY_HOSTS slice so future entries are one
  line. Test now also pins the spoofing-immune semantics
  ("evil.example/146.59.87.168:3000/x" must NOT match).

- core/archipelago/src/api/rpc/package/config.rs: split bitcoin
  from lnd in get_app_capabilities(). bitcoind never opens raw
  sockets — drop CAP_NET_RAW from bitcoin/bitcoin-core/bitcoin-knots.
  lnd/fedimint/fedimint-gateway keep it because they enumerate
  network interfaces during cert generation.

- core/archipelago/src/bootstrap.rs: tighten_secrets_dir()
  enforces 0700 on /var/lib/archipelago/secrets and 0600 on every
  file inside on each startup. The dir-mode is the load-bearing
  isolation boundary against rootless container escapes (their UID
  maps to >=100000, can't traverse uid=1000/0700). The per-file
  sweep is defense-in-depth against any installer that wrote 0644.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-05-01 08:59:11 -04:00
parent 0684491072
commit 2bf8181110
3 changed files with 87 additions and 18 deletions

View File

@@ -602,11 +602,16 @@ impl PodmanClient {
}
}
/// Registries we ship with as `--tls-verify=false` because they're internal
/// HTTP mirrors. Add a host:port here only if it's a controlled mirror that
/// the fleet trusts and operators won't ever paste a malicious URL into.
const INSECURE_REGISTRY_HOSTS: &[&str] = &["146.59.87.168:3000"];
pub fn image_uses_insecure_registry(image: &str) -> bool {
matches!(
image.split('/').next(),
Some("146.59.87.168:3000") | Some("23.182.128.160:3000")
)
image
.split('/')
.next()
.is_some_and(|host| INSECURE_REGISTRY_HOSTS.contains(&host))
}
fn podman_network_settings(
@@ -703,7 +708,10 @@ mod tests {
assert!(image_uses_insecure_registry(
"146.59.87.168:3000/lfg2025/bitcoin-knots:latest"
));
assert!(image_uses_insecure_registry(
// The legacy Hetzner mirror at 23.182.128.160 was decommissioned and
// is no longer trusted — it must NOT bypass TLS even if a stale
// registry config still references it.
assert!(!image_uses_insecure_registry(
"23.182.128.160:3000/lfg2025/filebrowser:v2.27.0"
));
assert!(!image_uses_insecure_registry(
@@ -712,6 +720,12 @@ mod tests {
assert!(!image_uses_insecure_registry(
"docker.io/library/nginx:latest"
));
// Spoofing immune: an attacker host that prefixes the trusted IP
// string into its own URL still has the attacker host in the
// registry-host slot, so it does NOT match.
assert!(!image_uses_insecure_registry(
"evil.example:80/146.59.87.168:3000/lfg2025/x:latest"
));
}
#[test]