mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Pre-cut documentation pass for the 0.4.0 release, verified against current source. Corrections: - fipsctl: stale 'show identities'/'show node' -> 'show status' (host-a-service, run-as-unprivileged-user) - mesh address derivation: first 16 bytes of SHA-256(pubkey) with the leading byte set to 0xfd, not a fixed fd97: prefix (reach-mesh-services, ipv6-adapter-walkthrough) - gateway control socket mode 0660 -> 0770 (troubleshoot-gateway) - Tor example: add advertised_port: 8443 so the published port matches the prose (enable-nostr-discovery) - bloom mesh-size estimate rewritten to the OR-union-of-peer-filters algorithm; plus mtu deep-link, gateway pool wording, and a NAT failure-mode line - examples: delete orphaned nostr-rs-relay config, accept inbound to the local 8443 TCP listener, fix fd::/8 -> fd00::/8 typos, dotless wireguard alias Additions: - new Nym mixnet transport section (fips-transport-layer) and the architecture transport list - new LAN/mDNS discovery section (fips-nostr-discovery) - reference docs: Nym transport, LAN discovery, and new control/stats surfaces; drop ble from the connect transport list
275 lines
9.3 KiB
Markdown
275 lines
9.3 KiB
Markdown
# FIPS Nostr Relay Sidecar
|
|
|
|
Runs a [strfry](https://github.com/hoytech/strfry) Nostr relay reachable
|
|
exclusively over the FIPS mesh. The relay container shares the FIPS
|
|
sidecar's network namespace and is isolated from the host network by
|
|
iptables — it can only be reached via the node's `.fips` name.
|
|
|
|
## How to Run
|
|
|
|
### 1. Set your node identity
|
|
|
|
The relay needs a unique FIPS identity. Generate one with:
|
|
|
|
```bash
|
|
fipsctl keygen
|
|
```
|
|
|
|
Then set it in `.env`:
|
|
|
|
```bash
|
|
# .env
|
|
FIPS_NSEC=nsec1... # paste your nsec here
|
|
```
|
|
|
|
`FIPS_NSEC` is required — the container will refuse to start without it.
|
|
|
|
### 2. Start the stack
|
|
|
|
FIPS is compiled from source inside the Docker build stage — no local Rust
|
|
toolchain, Zig, or cargo-zigbuild needed.
|
|
|
|
```bash
|
|
cd examples/sidecar-nostr-relay
|
|
docker compose up -d
|
|
```
|
|
|
|
This starts two containers that share a network namespace:
|
|
|
|
- **fips** — FIPS daemon + dnsmasq. Owns the namespace, creates `fips0`.
|
|
- **app** — strfry relay + nginx. Joins the namespace via `network_mode: service:fips`.
|
|
|
|
### 3. Verify
|
|
|
|
```bash
|
|
# FIPS node is up and has a mesh address:
|
|
docker exec sidecar-nostr-relay-fips-1 fipsctl show status
|
|
|
|
# Relay is listening (should show nginx on :80 and strfry on :7777):
|
|
docker exec sidecar-nostr-relay-fips-1 ss -tlnp
|
|
|
|
# Peer link to the public node is established:
|
|
docker exec sidecar-nostr-relay-fips-1 fipsctl show peers
|
|
|
|
# Or check the logs:
|
|
docker compose logs -f
|
|
```
|
|
|
|
### 4. Connect to the relay
|
|
|
|
Your node's npub (and therefore its `.fips` name) is derived from its keypair:
|
|
|
|
```bash
|
|
docker exec sidecar-nostr-relay-fips-1 fipsctl show status
|
|
```
|
|
|
|
Connect from any FIPS-peered client using the node's npub:
|
|
|
|
```
|
|
ws://npub1xxxx.fips:80
|
|
```
|
|
|
|
## Security Model
|
|
|
|
The sidecar pattern enforces strict network isolation on the app container:
|
|
|
|
- **No IPv4 access**: iptables blocks all eth0 traffic except the FIPS UDP
|
|
transport (port 2121), the local FIPS TCP listener (port 8443), and
|
|
outbound TCP to peers' published endpoints (port 443). The app container
|
|
cannot reach the Docker bridge, the host network, or any IPv4 address.
|
|
- **No IPv6 on eth0**: ip6tables blocks all IPv6 traffic on eth0. The app
|
|
container cannot use link-local or any Docker-assigned IPv6 addresses.
|
|
- **FIPS mesh only**: The only routable network path is through `fips0`
|
|
(`fd00::/8`). All application traffic traverses the FIPS mesh with
|
|
end-to-end encryption.
|
|
- **Loopback allowed**: `lo` is unrestricted for inter-process communication
|
|
within the shared namespace.
|
|
|
|
This means the app container treats the FIPS mesh as its sole network. Even
|
|
if the application is compromised, it cannot bypass the mesh or communicate
|
|
with the transport layer directly.
|
|
|
|
## Architecture
|
|
|
|
```text
|
|
┌───────────────────────────────────────────────────┐
|
|
│ Shared network namespace │
|
|
│ │
|
|
│ ┌───────────────┐ ┌──────────────────────────┐ │
|
|
│ │ fips-sidecar │ │ fips-app │ │
|
|
│ │ │ │ │ │
|
|
│ │ fips daemon │ │ your workload │ │
|
|
│ │ fipsctl │ │ │ │
|
|
│ │ dnsmasq │ │ │ │
|
|
│ └───────────────┘ └──────────────────────────┘ │
|
|
│ │
|
|
│ Interfaces: │
|
|
│ lo — loopback (unrestricted) │
|
|
│ eth0 — Docker bridge (iptables: FIPS only) │
|
|
│ fips0 — FIPS TUN (fd00::/8, unrestricted) │
|
|
└───────────────────────────────────────────────────┘
|
|
```
|
|
|
|
The FIPS sidecar owns the network namespace and creates the `fips0` TUN
|
|
interface. The app container joins via `network_mode: service:fips` and
|
|
sees the same interfaces. The entrypoint script applies iptables rules
|
|
before launching the FIPS daemon:
|
|
|
|
**IPv4 rules** (iptables):
|
|
|
|
- ACCEPT on `lo` (both directions)
|
|
- ACCEPT UDP sport/dport 2121 on `eth0` (FIPS UDP transport)
|
|
- ACCEPT TCP dport 443 / sport 443 on `eth0` (outbound to peers' TCP endpoints)
|
|
- ACCEPT TCP dport/sport 8443 on `eth0` (local FIPS TCP listener, `FIPS_TCP_BIND`)
|
|
- DROP everything else on `eth0`
|
|
|
|
**IPv6 rules** (ip6tables):
|
|
|
|
- ACCEPT on `lo` (both directions)
|
|
- ACCEPT on `fips0` (both directions)
|
|
- DROP everything on `eth0`
|
|
|
|
### DNS Resolution
|
|
|
|
DNS inside the container is handled by dnsmasq (127.0.0.1:53):
|
|
|
|
- `.fips` queries are forwarded to the FIPS daemon's built-in DNS resolver
|
|
(127.0.0.1:5354), which resolves npub-based names to `fd00::/8` addresses
|
|
- All other queries are forwarded to Docker's embedded DNS (127.0.0.11)
|
|
|
|
The `resolv.conf` mount points the container's resolver at 127.0.0.1,
|
|
where dnsmasq handles the routing.
|
|
|
|
## Run with Peers
|
|
|
|
To connect the sidecar to an existing mesh, provide the peer's npub and
|
|
transport address:
|
|
|
|
```bash
|
|
FIPS_PEER_NPUB=npub1... \
|
|
FIPS_PEER_ADDR=203.0.113.10:2121 \
|
|
FIPS_PEER_ALIAS=gateway \
|
|
docker compose up -d
|
|
```
|
|
|
|
Verify the peer link:
|
|
|
|
```bash
|
|
docker exec sidecar-nostr-relay-fips-1 fipsctl show peers
|
|
docker exec sidecar-nostr-relay-fips-1 fipsctl show links
|
|
```
|
|
|
|
## Verify Connectivity and Isolation
|
|
|
|
From the app container:
|
|
|
|
```bash
|
|
# Ping a mesh node by npub (resolves via .fips DNS):
|
|
docker exec sidecar-nostr-relay-app-1 ping6 -c3 npub1xxxx.fips
|
|
|
|
# Fetch a web page from some other mesh node over FIPS
|
|
# (:8000 is a stand-in for that node's own service; this relay serves :80):
|
|
docker exec sidecar-nostr-relay-app-1 curl -6 "http://npub1xxxx.fips:8000/"
|
|
|
|
# Docker bridge is blocked — this should fail:
|
|
docker exec sidecar-nostr-relay-app-1 ping -c1 -W2 172.20.0.13
|
|
|
|
# Loopback is allowed:
|
|
docker exec sidecar-nostr-relay-app-1 ping -c1 127.0.0.1
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `FIPS_NSEC` | *(required)* | Node secret key (hex or nsec1 bech32) |
|
|
| `FIPS_PEER_NPUB` | *(empty)* | Peer's npub to connect to |
|
|
| `FIPS_PEER_ADDR` | *(empty)* | Peer's transport address (e.g. `203.0.113.10:2121`) |
|
|
| `FIPS_PEER_ALIAS` | `peer` | Human-readable peer name |
|
|
| `FIPS_UDP_BIND` | `0.0.0.0:2121` | UDP transport bind address |
|
|
| `FIPS_TCP_BIND` | `0.0.0.0:8443` | TCP transport bind address |
|
|
| `FIPS_PEER_TRANSPORT` | `udp` | Peer transport type (`udp` or `tcp`) |
|
|
| `FIPS_TUN_MTU` | `1280` | TUN interface MTU |
|
|
| `FIPS_UDP_MTU` | `1472` | UDP transport MTU (default is Docker bridge IPv4 max; set to `1280` for IPv6-min-safe deploys) |
|
|
| `FIPS_NETWORK` | `fips-sidecar-net` | Docker network name (set to join external network) |
|
|
| `FIPS_SUBNET` | `172.20.1.0/24` | Docker network subnet |
|
|
| `FIPS_IPV4` | `172.20.1.20` | Sidecar's IPv4 address on the Docker network |
|
|
| `RUST_LOG` | `info` | FIPS log level |
|
|
|
|
## Troubleshooting
|
|
|
|
**`FIPS_NSEC is required`** — The `FIPS_NSEC` environment variable is not
|
|
set. Either add it to `.env` or pass it on the command line. Generate a
|
|
random key with: `openssl rand -hex 32`
|
|
|
|
**`fips0` interface not appearing** — The FIPS daemon needs `/dev/net/tun`
|
|
and `NET_ADMIN` capability. Check that the compose file includes both:
|
|
|
|
```yaml
|
|
cap_add:
|
|
- NET_ADMIN
|
|
devices:
|
|
- /dev/net/tun:/dev/net/tun
|
|
```
|
|
|
|
**No peer connection established** — Verify the peer address is reachable
|
|
from the sidecar container (`docker exec sidecar-nostr-relay-fips-1 ping -c1 <peer-ip>`).
|
|
If joining an external Docker network, ensure `FIPS_NETWORK`, `FIPS_SUBNET`,
|
|
and `FIPS_IPV4` match the target network. Check logs with
|
|
`docker logs sidecar-nostr-relay-fips-1`.
|
|
|
|
**DNS not resolving `.fips` names** — Verify dnsmasq is running:
|
|
`docker exec sidecar-nostr-relay-fips-1 pgrep dnsmasq`. Check that `resolv.conf` is
|
|
mounted (should contain `nameserver 127.0.0.1`). Verify the FIPS DNS
|
|
resolver is listening: `docker exec sidecar-nostr-relay-fips-1 dig @127.0.0.1 -p 5354 <npub>.fips AAAA`.
|
|
|
|
**iptables errors in entrypoint** — The sidecar container requires
|
|
`NET_ADMIN` capability for iptables. Without it, the isolation rules
|
|
cannot be applied and the entrypoint will fail.
|
|
|
|
## Production Considerations
|
|
|
|
**Secrets management**: The default `.env` contains a hardcoded nsec for
|
|
development. In production, use Docker secrets, a vault, or inject the key
|
|
via a secure CI/CD pipeline. Never commit production keys to version control.
|
|
|
|
**Logging**: Set `RUST_LOG` to control log verbosity (`debug`, `info`,
|
|
`warn`, `error`). For production, configure the Docker logging driver with
|
|
size limits:
|
|
|
|
```yaml
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
```
|
|
|
|
**Resource limits**: Add memory and CPU constraints in the compose file:
|
|
|
|
```yaml
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256M
|
|
cpus: "0.5"
|
|
```
|
|
|
|
**Multiple peers**: The entrypoint supports a single peer via environment
|
|
variables. For multiple peers, mount a custom `fips.yaml` directly:
|
|
|
|
```yaml
|
|
volumes:
|
|
- ./my-fips.yaml:/etc/fips/fips.yaml:ro
|
|
```
|
|
|
|
**Health checks**: Add a Docker health check using `fipsctl`:
|
|
|
|
```yaml
|
|
healthcheck:
|
|
test: ["CMD", "fipsctl", "show", "status"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
```
|