1 23 ADR 003 Network Exposure
Nimmo edited this page 2026-09-12 16:56:13 +01:00

ADR-003: Layer service exposure across local, mesh, and public ingress paths

  • Status: Accepted
  • Date: 2026-08-10
  • Scope: Service listeners, host and mesh firewalls, Docker bridges, public ingress, and relay access
  • Supersedes: None
  • Superseded by: None

Context

The fleet must serve local administration tools, machine-to-machine APIs, public web applications, media clients, and remote AI-agent sessions. Those clients do not share one network: they may be on host loopback, a Docker bridge, the physical LAN, the encrypted Nebula mesh, or the public internet through Pangolin/Newt and relay services.

Earlier configuration often used LAN addresses or global host firewall openings because they were convenient for Docker stacks and local clients. The Nebula rollout supplied stable encrypted addressing across LAN and remote locations, while Newt supplied outbound tunnels to a TLS public edge. The network-exposure review in Forgejo issue #16 then established that host posture is only a safe default: every listener still needs its own audience, bind, firewall, ingress, and authentication decision.

Subsequent changes demonstrated the intended layering. Laptop SSH was restricted to nebula.mesh; service references were progressively moved from LAN addresses to mesh addresses; Attic backends moved behind Newt-facing loopback listeners; and Paseo gained direct mesh listeners in addition to its authenticated TLS relay path. The present fleet still contains broad listeners and Lyra's mutable SWAG estate, so this decision describes a direction and review model rather than claiming universal completion.

Decision

Classify every listener by its intended audience and expose it through the narrowest path that satisfies that audience.

Local

Bind administration surfaces and same-host backends to a Unix socket or loopback when no remote client needs direct access. A local Newt client may publish a loopback application through Pangolin without making the application reachable on the LAN or mesh.

A container cannot use the host's loopback namespace. Where a tunnel or peer container needs access, prefer a named private Docker network or an explicitly reviewed bridge-gateway listener rather than widening the service to every host interface.

Mesh

Use a host's Nebula address and an explicit nebula.mesh firewall rule for trusted cross-host APIs and administration. Nebula supplies encrypted transport and stable identity/addressing; the NixOS interface firewall remains the service-level admission layer.

The mesh itself is not application authentication. Services with meaningful user, write, or control-plane authority should retain their own credentials where supported. The current permissive Nebula firewall makes explicit host firewall rules particularly important.

LAN

Use a physical-LAN listener only for a client or protocol that cannot practically use loopback, Nebula, or the ingress tunnel. A global allowedTCPPorts entry or 0.0.0.0 listener is a broad exposure class and requires a documented audience and compensating boundary.

Public

Public applications normally use outbound Newt tunnels to the Pangolin TLS edge. The application remains on loopback, a restricted host/bridge listener, or a private container network. Authentication may be enforced by Pangolin or the application, but the effective route must have a deliberate authentication boundary; “public” never means opening an unauthenticated application port directly to the internet.

Public relay services may connect authenticated remote clients to registered internal daemons. A relay is an additional route, not a reason to remove a useful direct mesh path. Paseo therefore supports both TLS relay access for location-independent clients and direct Nebula access for mesh clients.

Defaults do not grant access

A host role or exposure posture may supply conservative defaults, but it must not automatically open every service on that host. Listener and firewall choices remain explicit at service composition points. External Pangolin, public DNS, VPS, NAS, router, and SWAG state must be verified separately because Nix evaluation cannot attest to it.

Consequences

  • Remote administration and internal APIs can use stable mesh addresses rather than topology-dependent LAN addresses.
  • Host firewall rules remain necessary even though Nebula encrypts and filters overlay traffic.
  • Public web services can avoid direct inbound host exposure because Newt initiates the tunnel outbound.
  • The same service may intentionally have more than one path, such as direct mesh plus authenticated relay. Those paths must be inventoried separately.
  • Application authentication cannot be omitted merely because a service is on the mesh or a private Docker network, unless the network boundary is an explicit accepted control for that protocol.
  • Container-to-host traffic sometimes needs a bridge-specific listener and firewall rule; partial native migrations can therefore introduce extra exposure complexity.
  • External ingress health and policy are operational evidence, not facts that this repository alone can prove.
  • Existing global server SSH/Mosh rules, broad AI/media listeners, permissive mesh policy, and retained SWAG routes are known exceptions governed by the Security Baseline rather than silently normalised by this ADR.

Alternatives considered

Keep LAN addressing as the internal default

Rejected. It couples clients to one physical network, does not serve roaming hosts consistently, and bypasses the stable encrypted identity/address plane already provided by Nebula.

Trust the mesh and open services broadly within it

Rejected. A mesh member is not automatically authorised for every database, administrative UI, or write-capable API. Explicit interface firewall rules and application credentials limit lateral reach and make intent reviewable.

Send all access through public ingress or a relay

Rejected. It adds an external dependency and latency to same-mesh traffic and can turn an internet-edge outage into an internal outage. Relays and Pangolin solve public reachability; they do not replace direct local or mesh paths.

Open application ports directly on hosts

Rejected as the public default. Outbound tunnels give a central TLS/ingress boundary without router port forwarding or globally reachable application listeners. Direct protocol ports remain possible only where the protocol and client requirements justify them.

Let a host-level posture configure all service firewalls

Rejected. Services on the same host have different clients, authentication, and sensitivity. A posture can choose defaults but cannot substitute for an explicit service-level exposure decision.

References

  • Current routes and known exceptions: Network and Access Topology, Service Catalogue, and Security Baseline
  • Current implementation: modules/networking/nebula.nix, modules/services/newt.nix, service listener/firewall declarations, and host firewall declarations
  • Decision evidence: Forgejo issues #16, #23, and #73
  • Mesh-only laptop SSH: repository commit dc92c8a79bc03129f5774235eb772070db3d7219
  • Native Newt integration: repository commits ec51c750f19b84559fb8e26f484a7ceadf21f8cb and b355f1aee847e9713a164eaff609ee5441596689
  • Direct Paseo mesh path alongside relay access: repository commits c4c699c28cb99fa6c705084979d4ac303d090d41 and 3fcff8c8f85fbdb7845a025b7c5e05f0e17f35b2