mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
The gateway is designed for systems already serving DHCP and DNS to a LAN segment (canonically an OpenWrt AP). On those systems port 53 is already taken by the existing resolver, so the prior `[::]:53` default conflicted with the gateway's intended deployment target out of the box. The OpenWrt ipk previously overrode this in its packaged config as a workaround; matching the source default to what the canonical deployment actually wants makes the override redundant and removes a foot-gun for fresh manual Linux-host installs. The redundant `dns.listen` line in `packaging/openwrt-ipk/files/etc/fips/fips.yaml` is dropped along with this change. Operators on a host without a pre-existing resolver on port 53 can opt back into the wildcard bind by setting `dns.listen: "[::]:53"` explicitly. The new default binds IPv6 loopback only — Linux IPv6 sockets bound to explicit `::1` do not accept v4-mapped traffic, so forwarders that reach the gateway over IPv4 loopback need to be pointed at an explicit IPv4 listen address instead. Touches the gateway config struct and its default-value test, the commented-out gateway example in the Debian common fips.yaml, the OpenWrt ipk config (override removed), the gateway reference / how-to / design / tutorial / troubleshoot docs, and a CHANGELOG entry under [Unreleased] -> Changed.
459 lines
16 KiB
Markdown
459 lines
16 KiB
Markdown
# Deploy `fips-gateway` (Manual Linux-Host Setup)
|
|
|
|
`fips-gateway` is a separate service that runs alongside the FIPS
|
|
daemon and bridges a non-FIPS LAN to the FIPS mesh in two
|
|
independent directions: **outbound** (LAN clients reach mesh
|
|
services through DNS proxy + virtual-IP NAT) and **inbound** (mesh
|
|
peers reach LAN services through 1:1 port forwards on `fips0`).
|
|
This guide covers the **manual Linux-host** deployment path —
|
|
wiring DNS forwarding, route distribution, and firewall integration
|
|
on a server or non-OpenWrt router by hand.
|
|
|
|
> **Running OpenWrt?** Use the
|
|
> [tutorial](../tutorials/deploy-fips-gateway.md) instead. The OpenWrt
|
|
> ipk ships with the `gateway:` block pre-populated and the init
|
|
> script automates dnsmasq forwarding, RA route distribution, and the
|
|
> global IPv6 prefix on `br-lan`. The OpenWrt path is the canonical
|
|
> deployment of this feature; this how-to is the secondary path for
|
|
> operators with a different LAN-edge box (a Linux server already
|
|
> serving DHCP/DNS, a custom router distribution, etc.).
|
|
|
|
For the gateway design (NAT pipeline, virtual IP pool lifecycle, DNS
|
|
resolution flow), see [../design/fips-gateway.md](../design/fips-gateway.md).
|
|
For the full `gateway.*` configuration block, see the
|
|
[Gateway section](../reference/configuration.md#gateway-gateway) of
|
|
the configuration reference. For the `fips-gateway` binary's CLI
|
|
flags, see [../reference/cli-fips-gateway.md](../reference/cli-fips-gateway.md).
|
|
|
|
## The two halves
|
|
|
|
The gateway exposes two independent features that share a common
|
|
control plane (the same binary, the same nftables table `inet
|
|
fips_gateway`, the same control socket `/run/fips/gateway.sock`, the
|
|
same `gateway.*` config block). You can configure either half on its
|
|
own or both together.
|
|
|
|
- **Outbound gateway** (LAN → mesh). Non-FIPS LAN workstations resolve
|
|
`<npub>.fips` names against the gateway's DNS listener and receive
|
|
AAAA answers from the gateway's virtual-IP pool. Outbound traffic
|
|
to those addresses is DNAT'd to the real mesh address and SNAT'd
|
|
(masqueraded) onto `fips0` under the gateway's mesh identity. The
|
|
audience is unmodified LAN clients.
|
|
|
|
- **Inbound gateway** (mesh → LAN). A static `(listen_port, proto)
|
|
→ [target_addr]:target_port` table — configured in
|
|
`gateway.port_forwards[]` — exposes selected LAN services to the
|
|
mesh as `<gateway-npub>.fips:<listen_port>`. Mesh peers connect to
|
|
the gateway's mesh address; the gateway DNATs to the LAN target
|
|
and masquerades on the LAN side so return traffic flows through
|
|
conntrack. The audience is mesh peers reaching a service that
|
|
happens to live on this LAN.
|
|
|
|
The two halves are independent. Configure the outbound half if you
|
|
want LAN clients to *reach* the mesh; configure the inbound half if
|
|
you want mesh peers to *reach into* the LAN; configure both if you
|
|
want both.
|
|
|
|
## Common gateway-host setup
|
|
|
|
Both halves require the same host preparation. Work through this
|
|
section first, then jump to whichever half (or both) you need.
|
|
|
|
### FIPS daemon prerequisites
|
|
|
|
The gateway runs alongside a `fips` daemon on the same host:
|
|
|
|
- The daemon must be running with the TUN adapter enabled (the
|
|
`fips0` interface must exist).
|
|
- The daemon's DNS resolver must be enabled (`dns.enabled: true`,
|
|
default) and reachable from `fips-gateway`. By default that means
|
|
`[::1]:5354` (IPv6 loopback). The gateway's default
|
|
`dns.upstream` matches this; a v4 upstream like `127.0.0.1:5354`
|
|
cannot reach a daemon bound on `[::1]:5354` because Linux IPv6
|
|
sockets bound to explicit `::1` do not accept v4-mapped traffic.
|
|
|
|
If the daemon is not yet running with these features, set up the
|
|
daemon first — see [persistent-identity.md](persistent-identity.md)
|
|
and [../reference/configuration.md](../reference/configuration.md).
|
|
|
|
### Kernel sysctls
|
|
|
|
```sh
|
|
sudo sysctl -w net.ipv6.conf.all.forwarding=1
|
|
sudo sysctl -w net.ipv6.conf.all.proxy_ndp=1
|
|
```
|
|
|
|
`forwarding` lets the host route IPv6 packets between the LAN
|
|
interface and `fips0`. `proxy_ndp` lets the gateway answer Neighbor
|
|
Solicitation requests for virtual-pool addresses so LAN clients can
|
|
resolve their link-layer addresses (only relevant for the outbound
|
|
half, but harmless if you only run the inbound half).
|
|
|
|
Persist via a drop-in:
|
|
|
|
```sh
|
|
sudo tee /etc/sysctl.d/60-fips-gateway.conf <<'EOF'
|
|
net.ipv6.conf.all.forwarding = 1
|
|
net.ipv6.conf.all.proxy_ndp = 1
|
|
EOF
|
|
sudo sysctl --system
|
|
```
|
|
|
|
### Capability
|
|
|
|
`fips-gateway` requires `CAP_NET_ADMIN` to manage its nftables table
|
|
(`inet fips_gateway`) and proxy-NDP entries. The packaged systemd
|
|
unit (`fips-gateway.service`) runs as root, which satisfies this. For
|
|
non-package installs, set the file capability:
|
|
|
|
```sh
|
|
sudo setcap cap_net_admin+ep /usr/bin/fips-gateway
|
|
```
|
|
|
|
### Pool route
|
|
|
|
At startup `fips-gateway` adds `local <pool-cidr> dev lo` to the
|
|
local routing table. This tells the kernel to accept packets
|
|
destined for pool addresses as locally-owned, enabling the NAT
|
|
processing path. The route is cleaned up on shutdown. You do not
|
|
need to install it manually; if you see "destination unreachable"
|
|
errors for pool addresses on the gateway host, verify the route is
|
|
present:
|
|
|
|
```sh
|
|
ip -6 route show table local | grep <pool-cidr>
|
|
```
|
|
|
|
### Minimum configuration
|
|
|
|
In `/etc/fips/fips.yaml`, populate the `gateway` block with at minimum
|
|
`enabled: true`, `pool`, and `lan_interface`:
|
|
|
|
```yaml
|
|
gateway:
|
|
enabled: true
|
|
pool: "fd01::/112"
|
|
lan_interface: "enp3s0"
|
|
```
|
|
|
|
Pick a pool CIDR that does **not** overlap with any address space in
|
|
use on the LAN or in the mesh (the FIPS mesh occupies `fd00::/8`;
|
|
pick a different `fdXX::/N`). The `/112` size yields 65 536 virtual
|
|
IPs, which is the gateway's hard cap regardless of CIDR width.
|
|
|
|
This minimum config is enough to start the gateway. The `dns.*` block
|
|
is optional and defaults to `listen: "[::1]:5353"` and
|
|
`upstream: "[::1]:5354"`. The full block — including `dns.*`,
|
|
`pool_grace_period`, `conntrack.*`, and `port_forwards[]` — is
|
|
documented in
|
|
[../reference/configuration.md#gateway-gateway](../reference/configuration.md#gateway-gateway).
|
|
|
|
### Start the service
|
|
|
|
```sh
|
|
sudo systemctl enable --now fips-gateway
|
|
```
|
|
|
|
Verify the unit came up:
|
|
|
|
```sh
|
|
sudo systemctl status fips-gateway
|
|
sudo journalctl -u fips-gateway -e
|
|
```
|
|
|
|
The startup log will report `Gateway config loaded`,
|
|
`DNS upstream is reachable`, `Created nftables table 'fips_gateway'`,
|
|
and finally `fips-gateway running`. The unit's `ExecStartPre` waits up
|
|
to 30 s for `fips0` to appear, which covers the cold-boot race where
|
|
the daemon is still bringing up its TUN.
|
|
|
|
## Configure the outbound half
|
|
|
|
The outbound half lets LAN clients resolve `.fips` names and reach
|
|
mesh destinations. Three operator decisions are involved: pool CIDR,
|
|
DNS listen address, and how LAN clients learn the route to the pool
|
|
and the resolver address.
|
|
|
|
### Choose the pool CIDR
|
|
|
|
```yaml
|
|
gateway:
|
|
pool: "fd01::/112"
|
|
```
|
|
|
|
Constraints:
|
|
|
|
- Must not overlap with `fd00::/8` (the FIPS mesh address space).
|
|
- Must not overlap with any LAN-side IPv6 prefix already in use.
|
|
- `/112` is the practical width — wider just wastes address space
|
|
because the pool is hard-capped at 65 536 entries. Narrower is
|
|
fine if you want a smaller pool, but you'll reject DNS lookups
|
|
faster under churn.
|
|
|
|
### Choose the DNS listen address
|
|
|
|
```yaml
|
|
gateway:
|
|
dns:
|
|
listen: "[::1]:5353"
|
|
upstream: "[::1]:5354"
|
|
ttl: 60
|
|
```
|
|
|
|
Common cases:
|
|
|
|
- **Another resolver on the host (the canonical case):** the default
|
|
`listen: "[::1]:5353"` is loopback-only on an unprivileged port,
|
|
so it never conflicts with dnsmasq, systemd-resolved, or BIND
|
|
holding 53. Configure the existing resolver to forward `.fips`
|
|
queries to `[::1]:5353` and you are done — this is what the
|
|
OpenWrt ipk does automatically.
|
|
- **No other resolver on the host:** set `listen: "[::]:53"`
|
|
explicitly and LAN clients can query the gateway directly.
|
|
- **systemd-resolved is on port 53:** the default already side-steps
|
|
this — leave the listen address at `[::1]:5353` and configure the
|
|
stub or a small forwarder to delegate `.fips` to the gateway. If
|
|
you would rather have the gateway on 53 directly, disable the
|
|
systemd stub listener (`DNSStubListener=no` in
|
|
`/etc/systemd/resolved.conf`) and switch `listen` to `"[::]:53"`.
|
|
See
|
|
[troubleshoot-gateway.md](troubleshoot-gateway.md#port-conflict-on-the-dns-listen-port).
|
|
- **Bind on the LAN address only:** `listen: "192.168.1.1:53"`
|
|
exposes the resolver only to LAN clients, not loopback.
|
|
|
|
The gateway returns `REFUSED` for any non-`.fips` query — clients
|
|
that point at it directly need a fallback resolver, or you should
|
|
front it with a stub forwarder.
|
|
|
|
### Distribute the route to LAN clients
|
|
|
|
Each LAN client must route the gateway's pool CIDR to the gateway's
|
|
LAN-side IPv6 address. Three options, in order of preference for
|
|
production:
|
|
|
|
- **RA Route Information Option** (RFC 4191). If the LAN's RA daemon
|
|
(`radvd`, `dnsmasq --enable-ra`, OpenWrt's `odhcpd`) supports
|
|
publishing route options, configure it to advertise the pool CIDR
|
|
with the gateway as next-hop. Clients pick this up automatically.
|
|
|
|
- **Static route on the LAN router**. If clients route through a
|
|
central LAN router, add a static route entry there — the router
|
|
then handles forwarding to the gateway. The exact syntax depends
|
|
on the router OS.
|
|
|
|
- **Per-host static route** (testing or single-client deployments):
|
|
|
|
```sh
|
|
sudo ip -6 route add fd01::/112 via fe80::<gateway-link-local>%<iface>
|
|
# or, if the gateway has a stable global LAN address:
|
|
sudo ip -6 route add fd01::/112 via <gateway-lan-addr>
|
|
```
|
|
|
|
### Distribute the resolver to LAN clients
|
|
|
|
LAN clients also need to send `.fips` queries to the gateway. Two
|
|
patterns:
|
|
|
|
- **Forward `.fips` from the LAN's main resolver.** If the LAN runs
|
|
Pi-hole, Unbound, dnsmasq, or systemd-resolved as the central
|
|
resolver, configure a conditional forward for `fips.`. Unbound
|
|
example:
|
|
|
|
```text
|
|
forward-zone:
|
|
name: "fips."
|
|
forward-addr: <gateway-lan-addr>@53
|
|
```
|
|
|
|
dnsmasq example:
|
|
|
|
```text
|
|
server=/fips/<gateway-lan-addr>
|
|
```
|
|
|
|
Clients keep their existing DNS settings; only `.fips` queries are
|
|
diverted.
|
|
|
|
- **Point clients directly at the gateway.** Simpler for testing,
|
|
but the gateway returns `REFUSED` for non-`.fips` queries, so each
|
|
client must also have a fallback resolver configured.
|
|
|
|
### Verify the outbound path
|
|
|
|
From a LAN client:
|
|
|
|
```sh
|
|
dig @<gateway-lan-addr> hostname.fips AAAA
|
|
# Expect an AAAA from the pool CIDR
|
|
|
|
ping6 hostname.fips
|
|
# Should succeed via the gateway
|
|
```
|
|
|
|
If either step fails, see
|
|
[troubleshoot-gateway.md](troubleshoot-gateway.md#outbound-half-diagnostics).
|
|
|
|
## Configure the inbound half
|
|
|
|
The inbound half exposes a LAN-side service to mesh peers. Configured
|
|
under `gateway.port_forwards[]`:
|
|
|
|
```yaml
|
|
gateway:
|
|
port_forwards:
|
|
- listen_port: 8080
|
|
proto: tcp
|
|
target: "[fd12:3456::10]:80"
|
|
- listen_port: 2222
|
|
proto: tcp
|
|
target: "[fd12:3456::20]:22"
|
|
- listen_port: 5353
|
|
proto: udp
|
|
target: "[fd12:3456::10]:53"
|
|
```
|
|
|
|
Field reference:
|
|
|
|
- `listen_port` — port on the gateway's `fips0` mesh-side address
|
|
that mesh peers connect to. Must be non-zero. Each
|
|
`(listen_port, proto)` pair must be unique across the list (the
|
|
same port on TCP and UDP is allowed; the same port twice on the
|
|
same proto is rejected at config-load time).
|
|
- `proto` — `tcp` or `udp`.
|
|
- `target` — IPv6 LAN destination as `[addr]:port`. IPv4 targets are
|
|
rejected at parse time by the YAML deserializer (the field is
|
|
typed `SocketAddrV6`). If the LAN host is reachable only by IPv4,
|
|
put a small IPv6-aware reverse proxy in front of it on the gateway
|
|
itself.
|
|
|
|
### Worked example: HTTP and DNS
|
|
|
|
Suppose the gateway runs on a LAN with an HTTP server at
|
|
`[fd12:3456::10]:80` and a recursive resolver at
|
|
`[fd12:3456::10]:53`, and you want mesh peers to reach them as
|
|
`<gateway-npub>.fips:8080` (HTTP) and `<gateway-npub>.fips:5353`
|
|
(DNS). Add to the gateway's `fips.yaml`:
|
|
|
|
```yaml
|
|
gateway:
|
|
port_forwards:
|
|
- listen_port: 8080
|
|
proto: tcp
|
|
target: "[fd12:3456::10]:80"
|
|
- listen_port: 5353
|
|
proto: udp
|
|
target: "[fd12:3456::10]:53"
|
|
```
|
|
|
|
Reload:
|
|
|
|
```sh
|
|
sudo systemctl restart fips-gateway
|
|
```
|
|
|
|
From any mesh peer (the host name `gateway` is whatever the gateway's
|
|
npub maps to in the local `hosts` file or via Nostr advert):
|
|
|
|
```sh
|
|
curl http://gateway.fips:8080/
|
|
dig @gateway.fips -p 5353 example.com A
|
|
```
|
|
|
|
Each mesh-side request enters `fips0` on the listen port, gets DNAT'd
|
|
to the LAN target, and the LAN-side masquerade rule rewrites the
|
|
source to the gateway's LAN address so return traffic flows back
|
|
through conntrack.
|
|
|
|
### Compose with the mesh firewall
|
|
|
|
`gateway.port_forwards[]` opens *mesh-side* listeners on `fips0`. If
|
|
the host's mesh firewall is enabled (see
|
|
[enable-mesh-firewall.md](enable-mesh-firewall.md)), inbound TCP/UDP
|
|
on `fips0` for these ports must be permitted in the baseline or via
|
|
a drop-in. The default baseline allows established/related and
|
|
ICMPv6 only, so without an explicit allow rule, mesh peers will see
|
|
TCP RSTs or silent drops on the listen port.
|
|
|
|
A typical drop-in for the worked example:
|
|
|
|
```nft
|
|
# /etc/fips/fips.d/gateway-inbound.nft
|
|
tcp dport 8080 accept
|
|
udp dport 5353 accept
|
|
```
|
|
|
|
Reload the firewall:
|
|
|
|
```sh
|
|
sudo systemctl reload-or-restart fips-firewall.service
|
|
```
|
|
|
|
If the inbound half doesn't need access control beyond the listen
|
|
port itself, no source filter is needed. To restrict to specific
|
|
mesh peers, follow the `ip6 saddr <addr> tcp dport <port> accept`
|
|
pattern from the firewall guide.
|
|
|
|
### Verify the inbound path
|
|
|
|
From a mesh peer (any FIPS node):
|
|
|
|
```sh
|
|
curl -v http://<gateway-npub>.fips:8080/
|
|
```
|
|
|
|
A successful response confirms the full path: mesh ingress on
|
|
`fips0`, DNAT to the LAN target, LAN-side masquerade, and conntrack-
|
|
tracked return. If it fails, see
|
|
[troubleshoot-gateway.md](troubleshoot-gateway.md#inbound-half-diagnostics).
|
|
|
|
## Operate and verify
|
|
|
|
`fips-gateway` exposes its own control socket at
|
|
`/run/fips/gateway.sock`, separate from the daemon's
|
|
`/run/fips/control.sock`. There is no `fipsctl gateway` subcommand —
|
|
talk to it directly:
|
|
|
|
```sh
|
|
echo '{"command":"show_gateway"}' | sudo nc -U /run/fips/gateway.sock
|
|
echo '{"command":"show_mappings"}' | sudo nc -U /run/fips/gateway.sock
|
|
```
|
|
|
|
`show_gateway` returns pool counters (`pool_total`, `pool_allocated`,
|
|
`pool_active`, `pool_draining`, `pool_free`), `nat_mappings`,
|
|
`dns_listen`, `uptime_secs`, and the active config snapshot.
|
|
`show_mappings` returns the per-allocation list with virtual IP, mesh
|
|
address, npub-derived `node_addr`, dns name, state (`Allocated`,
|
|
`Active`, `Draining`), session count, and ages. For the full schema
|
|
see [../reference/control-socket.md#gateway-command-catalog](../reference/control-socket.md#gateway-command-catalog).
|
|
|
|
The journal is the other primary signal:
|
|
|
|
```sh
|
|
sudo systemctl status fips-gateway
|
|
sudo journalctl -u fips-gateway -e
|
|
```
|
|
|
|
Expect `MappingCreated`/`MappingRemoved` debug lines as DNS-driven
|
|
allocations come and go (run with `--log-level debug` to see them),
|
|
and `Final pool status` on shutdown. Errors in adding NAT rules or
|
|
proxy-NDP entries surface here.
|
|
|
|
## See also
|
|
|
|
- [../tutorials/deploy-fips-gateway.md](../tutorials/deploy-fips-gateway.md) —
|
|
the canonical, package-driven OpenWrt deployment path.
|
|
- [../design/fips-gateway.md](../design/fips-gateway.md) — gateway
|
|
design, NAT pipeline, virtual IP pool lifecycle, security
|
|
considerations.
|
|
- [Gateway section](../reference/configuration.md#gateway-gateway) of
|
|
the configuration reference — full `gateway.*` block.
|
|
- [../reference/cli-fips-gateway.md](../reference/cli-fips-gateway.md) —
|
|
`fips-gateway` binary CLI flags.
|
|
- [Gateway command catalog](../reference/control-socket.md#gateway-command-catalog)
|
|
in the control-socket reference — JSON schema for `show_gateway`
|
|
and `show_mappings`.
|
|
- [troubleshoot-gateway.md](troubleshoot-gateway.md) — diagnostic
|
|
recipes grouped by half.
|
|
- [enable-mesh-firewall.md](enable-mesh-firewall.md) — mesh-firewall
|
|
baseline and drop-ins (needed when exposing inbound ports).
|