How the network is closed
Only one part of sandcastle reaches the network: the gate. The core and the boxes have no way out of their own, and the keep, which adds the secrets, reaches only the gate. This page explains how the gate decides what passes, and why the internal network stays closed even if it did not.
flowchart LR
core["core"]
keep["keep"]
gate["gate<br/>Smokescreen"]
dest["destinations"]
inside["internal network"]
core -- "Unix socket" --> keep
keep -- "sandcastle-link<br/>internal, no DNS" --> gate
gate -- "sandcastle-egress" --> dest
gate -. "refused after resolving,<br/>and no route" .-x inside
The core reaches the keep through a Unix socket. The keep reaches the gate on sandcastle-link,
a network with no way out and no DNS. The gate reaches the destinations on sandcastle-egress,
and the internal network is closed to it twice: the gate refuses it after resolving a name, and
the egress network has no route to it.
Who speaks to whom
- A box has no network at all: Podman starts it with
--network none. - The core has no secrets and names a destination, never an address. Everything it sends,
the polls of the sources, the results and the requests of
claude, goes to the keep. - The keep sits only on
sandcastle-link, with no DNS and IPv6 turned off. It cannot resolve a name or leave on its own: it asks the gate for a tunnel withCONNECTto the destination’s name and port as the configuration writes them, and to nothing else. Inside the tunnel it always verifies TLS against the root certificates of its image, and it never follows a redirect. - The gate is Smokescreen, and the only part with a way out. It lets through only the names of the destinations, and it alone resolves them.
The first barrier: the gate refuses after resolving
The gate’s list of names is made from the destinations of the configuration, in one rule, the
default; there are no roles to fall into. A name not in it is refused. Changing a destination
asks for a restart of the gate: it never reads its list again while it runs.
DNS is not a barrier: a name in the list may resolve to anything, and the answer may change from
one second to the next. So the gate resolves the name itself, for IPv4 only, and checks the
address it is about to connect to. An address in the internal network (private, CGNAT,
loopback, link-local) is refused, with a reason, and the refusal is recorded. sandcastle check shows the recent refusals and their likely
cause, and the job that met one goes back to its source to be run later, since the job could not
have caused it.
The second barrier: no route inside
The gate is a program, and a program can have flaws. So under it, the network it leaves through
has no way inside. When the gate starts, sandcastle recreates sandcastle-egress as an IPv4-only
network with a prohibit route for each of the fourteen IPv4 ranges of the IANA’s bogons, the
internal ranges among them. A connection to any of them fails at once, whatever the gate decided.
IPv6 is turned off in the gate.
The gate runs without CAP_NET_ADMIN, so it cannot remove a route. sandcastle reads the network
back before the gate starts, and the gate does not start when a route is missing; check holds
the running network to the same routes.
An internal exception
internal: ip:port in a destination opens one service of your network for that destination
alone. It cannot be a range, and never a sealed address: loopback, link-local (where the cloud
metadata address lives), NAT64, 6to4, Teredo, IPv4 mapped into IPv6, multicast, the unspecified
address, or the two networks of the installation.
The exception opens the address at both barriers: the gate lets it through, and the egress
network gets a /32 route to it. The route opens the whole IP, every port. The port is held by
the keep, which only ever asks for the one in the configuration; the name is held by the keep’s
TLS, so another destination pointed at the same address fails before its secret is sent.
check warns of each exception, and of http:// beside one, which nothing encrypts and which
is outside the promise.
What a tampered gate can still do
A gate that was tampered with is outside the promise: it is part of sandcastle. It could connect anywhere its network lets it, but the second barrier holds it: it gets as far as Podman’s own addresses on the egress network, and no further.