Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 with CONNECT to 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.