fix(security): drop unbindable publish binds instead of crash-looping the app

The dd61a204 bind hardening published Bitcoin RPC on the archy-net gateway
10.89.0.1 — but rootlessport binds in the HOST netns, where that address
does not exist. First real deploy (.228, 2026-07-09) crash-looped
bitcoin-knots AND bitcoin-core the moment the gate's stop→start regenerated
the unit from the re-signed catalog: 'rootlessport listen tcp
10.89.0.1:8332: bind: cannot assign requested address', restart counter 132.
Hand-edits to the unit don't survive — the orchestrator regenerates it from
the signed catalog manifest within seconds.

- New archipelago_container::manifest::host_can_bind_publish_ip(): empty/
  wildcard/loopback accepted without probing, anything else ephemeral-bind
  probed. Applied at all three publish paths — quadlet from_manifest
  (PublishPort), podman API create (host_ip), and the legacy -p string
  table loop — each dropping the publish with a warn instead of taking the
  container down. This neutralizes the bad binds already in the SIGNED
  catalog, so nodes recover on binary deploy alone (no ceremony needed).
- Remove the gateway publishes from bitcoin-knots/-core manifests and the
  legacy config.rs tables: verified on .228 that every in-node consumer
  (lnd, btcpay/nbxplorer, fedimint, mempool-api) dials the container's
  archy-net alias directly (bitcoin-knots:8332) and lnd uses RPC polling
  (no ZMQ) — the gateway publish had zero consumers. Loopback-only RPC/ZMQ
  (the approved LAN lockdown) stands; P2P 8333 stays public.

Catalog still ships the gateway binds until the next signing ceremony
regenerates it from these manifests; the guard makes that non-urgent.

Tests: container crate 65/65 (2 new guard tests), quadlet 40/40.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-08 23:41:39 -04:00
co-authored by Claude Fable 5
parent 922b79bf95
commit eb6ec71a56
7 changed files with 110 additions and 28 deletions
+7 -8
View File
@@ -71,18 +71,17 @@ app:
network_policy: isolated
ports:
# RPC is auth-only: publish host-local + the archy-net gateway
# (host.archipelago / host.containers.internal -> 10.89.0.1) so in-node
# consumers (lnd, fedimint-gateway, btcpay) keep their backend-agnostic
# endpoint - but the LAN can no longer reach nodeIP:8332. P2P stays public.
# RPC is auth-only: publish host-local ONLY - the LAN cannot reach
# nodeIP:8332. In-node consumers (lnd, fedimint, btcpay, mempool-api)
# dial the container's archy-net alias directly (bitcoin-core:8332),
# which needs no publish at all. Do NOT bind the archy-net gateway
# (10.89.0.1): rootlessport binds in the HOST netns where that address
# does not exist, and the whole unit crash-loops (2026-07-09, .228).
# P2P 8333 stays public.
- host: 8332
container: 8332
protocol: tcp
bind: 127.0.0.1
- host: 8332
container: 8332
protocol: tcp
bind: 10.89.0.1
- host: 8333
container: 8333
protocol: tcp
+7 -8
View File
@@ -71,18 +71,17 @@ app:
network_policy: isolated
ports:
# RPC is auth-only: publish host-local + the archy-net gateway
# (host.archipelago / host.containers.internal -> 10.89.0.1) so in-node
# consumers (lnd, fedimint-gateway, btcpay) keep their backend-agnostic
# endpoint - but the LAN can no longer reach nodeIP:8332. P2P stays public.
# RPC is auth-only: publish host-local ONLY - the LAN cannot reach
# nodeIP:8332. In-node consumers (lnd, fedimint, btcpay, mempool-api)
# dial the container's archy-net alias directly (bitcoin-knots:8332),
# which needs no publish at all. Do NOT bind the archy-net gateway
# (10.89.0.1): rootlessport binds in the HOST netns where that address
# does not exist, and the whole unit crash-loops (2026-07-09, .228).
# P2P 8333 stays public.
- host: 8332
container: 8332
protocol: tcp
bind: 127.0.0.1
- host: 8332
container: 8332
protocol: tcp
bind: 10.89.0.1
- host: 8333
container: 8333
protocol: tcp