mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-22 07:48:26 +00:00
Add sidecar-nostr-relay example and fix openwrt CI path
Add a complete example running a strfry Nostr relay exclusively over the FIPS mesh using the sidecar pattern. Includes Docker Compose stack, network isolation via iptables, .fips DNS resolution, nak CLI, build script with cross-compilation support, and documentation. Also fix the package-openwrt.yml workflow path to match the openwrt → openwrt-ipk directory rename.
This commit is contained in:
2
.github/workflows/package-openwrt.yml
vendored
2
.github/workflows/package-openwrt.yml
vendored
@@ -96,7 +96,7 @@ jobs:
|
||||
env:
|
||||
PKG_VERSION: ${{ steps.version.outputs.version }}
|
||||
LLVM_STRIP: llvm-strip
|
||||
run: ./packaging/openwrt/build-ipk.sh --arch ${{ matrix.build_arch }}
|
||||
run: ./packaging/openwrt-ipk/build-ipk.sh --arch ${{ matrix.build_arch }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
12
examples/sidecar-nostr-relay/.env
Normal file
12
examples/sidecar-nostr-relay/.env
Normal file
@@ -0,0 +1,12 @@
|
||||
# FIPS sidecar default configuration.
|
||||
# Override these values or create a .env.local file.
|
||||
|
||||
# Node identity — generate with: fipsctl keygen
|
||||
# Must be set before running: export FIPS_NSEC=<your-nsec>
|
||||
FIPS_NSEC=
|
||||
|
||||
# Peer configuration (leave empty for standalone operation)
|
||||
FIPS_PEER_NPUB=npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98
|
||||
FIPS_PEER_ADDR=217.77.8.91:2121
|
||||
FIPS_PEER_ALIAS=vps
|
||||
FIPS_PEER_TRANSPORT=udp
|
||||
3
examples/sidecar-nostr-relay/.gitignore
vendored
Normal file
3
examples/sidecar-nostr-relay/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fips
|
||||
fipsctl
|
||||
fipstop
|
||||
53
examples/sidecar-nostr-relay/Dockerfile
Normal file
53
examples/sidecar-nostr-relay/Dockerfile
Normal file
@@ -0,0 +1,53 @@
|
||||
FROM debian:trixie-slim
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
iproute2 iputils-ping dnsutils dnsmasq iptables \
|
||||
openssh-client openssh-server python3 \
|
||||
tcpdump netcat-openbsd curl iperf3 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install nak (Nostr Army Knife) — detect arch and download the correct binary.
|
||||
# Asset naming: nak-<version>-linux-<arch>
|
||||
RUN ARCH=$(dpkg --print-architecture) && \
|
||||
case "$ARCH" in \
|
||||
amd64) NAK_ARCH="linux-amd64" ;; \
|
||||
arm64) NAK_ARCH="linux-arm64" ;; \
|
||||
armhf) NAK_ARCH="linux-arm" ;; \
|
||||
*) echo "Unsupported arch: $ARCH" && exit 1 ;; \
|
||||
esac && \
|
||||
NAK_VERSION=$(curl -sSL --retry 3 \
|
||||
"https://api.github.com/repos/fiatjaf/nak/releases/latest" \
|
||||
| grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\(.*\)".*/\1/') && \
|
||||
echo "Installing nak ${NAK_VERSION} for ${NAK_ARCH}" && \
|
||||
curl -sSL --retry 3 \
|
||||
"https://github.com/fiatjaf/nak/releases/download/${NAK_VERSION}/nak-${NAK_VERSION}-${NAK_ARCH}" \
|
||||
-o /usr/local/bin/nak && \
|
||||
chmod +x /usr/local/bin/nak && \
|
||||
nak --version
|
||||
|
||||
# Setup SSH server with no authentication (test only!)
|
||||
RUN mkdir -p /var/run/sshd && \
|
||||
ssh-keygen -A && \
|
||||
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
|
||||
sed -i 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config && \
|
||||
sed -i 's/UsePAM yes/UsePAM no/' /etc/ssh/sshd_config && \
|
||||
passwd -d root
|
||||
|
||||
# dnsmasq: forward .fips to FIPS daemon, everything else to Docker DNS
|
||||
RUN printf '%s\n' \
|
||||
'port=53' \
|
||||
'listen-address=127.0.0.1' \
|
||||
'bind-interfaces' \
|
||||
'server=/fips/127.0.0.1#5354' \
|
||||
'server=127.0.0.11' \
|
||||
'no-resolv' \
|
||||
>> /etc/dnsmasq.conf
|
||||
|
||||
COPY fips fipsctl fipstop /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/fips /usr/local/bin/fipsctl /usr/local/bin/fipstop
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
49
examples/sidecar-nostr-relay/Dockerfile.app
Normal file
49
examples/sidecar-nostr-relay/Dockerfile.app
Normal file
@@ -0,0 +1,49 @@
|
||||
# strfry: official multi-arch image (linux/amd64 + linux/arm64)
|
||||
# replaces the nostr-rs-relay source build which was amd64-only.
|
||||
#
|
||||
# IMPORTANT: use alpine (same musl-based libc as the strfry source image) as
|
||||
# the final stage. Copying the strfry binary into a glibc-based image such as
|
||||
# debian:bookworm-slim causes "not found" at exec time because the musl dynamic
|
||||
# linker (/lib/ld-musl-*.so.1) and its shared libraries are absent there.
|
||||
FROM ghcr.io/hoytech/strfry:latest AS strfry
|
||||
|
||||
FROM alpine:3.18
|
||||
|
||||
RUN apk add --no-cache nginx
|
||||
|
||||
# nginx: reverse proxy port 80 (IPv4 + IPv6) → strfry 127.0.0.1:7777
|
||||
RUN printf 'server {\n\
|
||||
listen 80;\n\
|
||||
listen [::]:80;\n\
|
||||
location / {\n\
|
||||
proxy_pass http://127.0.0.1:7777;\n\
|
||||
proxy_http_version 1.1;\n\
|
||||
proxy_read_timeout 1d;\n\
|
||||
proxy_send_timeout 1d;\n\
|
||||
proxy_set_header Upgrade $http_upgrade;\n\
|
||||
proxy_set_header Connection "Upgrade";\n\
|
||||
proxy_set_header Host $host;\n\
|
||||
}\n\
|
||||
}\n' > /etc/nginx/http.d/nostr-relay.conf && \
|
||||
rm -f /etc/nginx/http.d/default.conf
|
||||
|
||||
COPY --from=strfry /app/strfry /usr/local/bin/strfry
|
||||
# Copy musl shared libraries that strfry depends on from the source image.
|
||||
COPY --from=strfry \
|
||||
/usr/lib/liblmdb.so.0 \
|
||||
/usr/lib/libcrypto.so.50 \
|
||||
/usr/lib/libssl.so.53 \
|
||||
/usr/lib/libsecp256k1.so.2 \
|
||||
/usr/lib/libzstd.so.1 \
|
||||
/usr/lib/libstdc++.so.6 \
|
||||
/usr/lib/libgcc_s.so.1 \
|
||||
/usr/lib/
|
||||
COPY --from=strfry /lib/libz.so.1 /lib/
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
RUN mkdir -p strfry-db
|
||||
|
||||
RUN printf '#!/bin/sh\nnginx\nexec strfry relay\n' > /entrypoint-app.sh && \
|
||||
chmod +x /entrypoint-app.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint-app.sh"]
|
||||
289
examples/sidecar-nostr-relay/README.md
Normal file
289
examples/sidecar-nostr-relay/README.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# 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. Build the FIPS binaries
|
||||
|
||||
From the repo root, compile FIPS for Linux and copy the binaries into the
|
||||
Docker build context:
|
||||
|
||||
```bash
|
||||
cd examples/sidecar-nostr-relay
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
Cross-compilation from macOS is supported via `cargo-zigbuild`.
|
||||
|
||||
### 2. 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.
|
||||
|
||||
### 3. Start the stack
|
||||
|
||||
```bash
|
||||
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`.
|
||||
|
||||
### 4. 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
|
||||
```
|
||||
|
||||
### 5. 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 FIPS UDP
|
||||
transport (port 2121) and TCP transport (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`
|
||||
(`fd::/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 (fd::/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` (FIPS TCP transport)
|
||||
- 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 `fd::/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.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cd examples/sidecar-nostr-relay
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
This compiles FIPS for Linux, copies the binaries into the Docker context,
|
||||
and builds the sidecar and app images. Cross-compilation from macOS is
|
||||
supported via `cargo-zigbuild`.
|
||||
|
||||
## 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 a mesh node over FIPS:
|
||||
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_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
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
# Override: use a pre-existing external network instead of creating one.
|
||||
# Used by test-sidecar.sh for multi-instance testing on a shared network.
|
||||
networks:
|
||||
fips-net:
|
||||
external: true
|
||||
name: ${FIPS_NETWORK:-fips-sidecar-net}
|
||||
54
examples/sidecar-nostr-relay/docker-compose.yml
Normal file
54
examples/sidecar-nostr-relay/docker-compose.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
networks:
|
||||
fips-net:
|
||||
name: ${FIPS_NETWORK:-fips-sidecar-net}
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: ${FIPS_SUBNET:-172.20.1.0/24}
|
||||
|
||||
services:
|
||||
fips:
|
||||
build:
|
||||
context: .
|
||||
hostname: fips-sidecar
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
sysctls:
|
||||
- net.ipv6.conf.all.disable_ipv6=0
|
||||
restart: "no"
|
||||
ports:
|
||||
- "2121:2121/udp" # FIPS UDP transport
|
||||
- "8443:8443/tcp" # FIPS TCP transport
|
||||
- "80:80/tcp" # Nostr relay WebSocket (via nginx)
|
||||
environment:
|
||||
- RUST_LOG=${RUST_LOG:-info}
|
||||
- FIPS_NSEC=${FIPS_NSEC}
|
||||
- FIPS_PEER_NPUB=${FIPS_PEER_NPUB:-}
|
||||
- FIPS_PEER_ADDR=${FIPS_PEER_ADDR:-}
|
||||
- FIPS_PEER_ALIAS=${FIPS_PEER_ALIAS:-peer}
|
||||
- FIPS_UDP_BIND=${FIPS_UDP_BIND:-0.0.0.0:2121}
|
||||
- FIPS_TUN_MTU=${FIPS_TUN_MTU:-1280}
|
||||
- FIPS_PEER_TRANSPORT=${FIPS_PEER_TRANSPORT:-udp}
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: ${FIPS_IPV4:-172.20.1.20}
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.app
|
||||
network_mode: "service:fips"
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- fips
|
||||
volumes:
|
||||
- relay-data:/usr/src/app/strfry-db
|
||||
- ./relay/strfry.conf:/usr/src/app/strfry.conf:ro
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
|
||||
volumes:
|
||||
relay-data:
|
||||
86
examples/sidecar-nostr-relay/entrypoint.sh
Executable file
86
examples/sidecar-nostr-relay/entrypoint.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# FIPS sidecar entrypoint: generate config, apply iptables isolation, launch FIPS.
|
||||
set -e
|
||||
|
||||
# --- Generate FIPS config from environment variables ---
|
||||
|
||||
FIPS_NSEC="${FIPS_NSEC:?FIPS_NSEC is required}"
|
||||
FIPS_UDP_BIND="${FIPS_UDP_BIND:-0.0.0.0:2121}"
|
||||
FIPS_TCP_BIND="${FIPS_TCP_BIND:-0.0.0.0:8443}"
|
||||
FIPS_TUN_MTU="${FIPS_TUN_MTU:-1280}"
|
||||
FIPS_PEER_TRANSPORT="${FIPS_PEER_TRANSPORT:-udp}"
|
||||
|
||||
mkdir -p /etc/fips
|
||||
|
||||
# Build peers section
|
||||
PEERS_SECTION=""
|
||||
if [ -n "$FIPS_PEER_NPUB" ] && [ -n "$FIPS_PEER_ADDR" ]; then
|
||||
FIPS_PEER_ALIAS="${FIPS_PEER_ALIAS:-peer}"
|
||||
PEERS_SECTION=" - npub: \"${FIPS_PEER_NPUB}\"
|
||||
alias: \"${FIPS_PEER_ALIAS}\"
|
||||
addresses:
|
||||
- transport: ${FIPS_PEER_TRANSPORT}
|
||||
addr: \"${FIPS_PEER_ADDR}\"
|
||||
connect_policy: auto_connect"
|
||||
fi
|
||||
|
||||
cat > /etc/fips/fips.yaml <<EOF
|
||||
node:
|
||||
identity:
|
||||
nsec: "${FIPS_NSEC}"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: ${FIPS_TUN_MTU}
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "${FIPS_UDP_BIND}"
|
||||
mtu: 1472
|
||||
tcp:
|
||||
bind_addr: "${FIPS_TCP_BIND}"
|
||||
|
||||
peers:
|
||||
${PEERS_SECTION:- []}
|
||||
EOF
|
||||
|
||||
echo "Generated /etc/fips/fips.yaml"
|
||||
|
||||
# --- Apply iptables rules for strict network isolation ---
|
||||
#
|
||||
# Goal: only FIPS UDP transport (port 2121) may use eth0.
|
||||
# All other eth0 traffic is dropped. fips0 and loopback are unrestricted.
|
||||
# This ensures the app container (sharing this network namespace) can only
|
||||
# communicate over the FIPS mesh.
|
||||
|
||||
# IPv4: allow only FIPS transport on eth0
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
iptables -A OUTPUT -o eth0 -p udp --dport 2121 -j ACCEPT
|
||||
iptables -A OUTPUT -o eth0 -p udp --sport 2121 -j ACCEPT
|
||||
iptables -A INPUT -i eth0 -p udp --dport 2121 -j ACCEPT
|
||||
iptables -A INPUT -i eth0 -p udp --sport 2121 -j ACCEPT
|
||||
iptables -A OUTPUT -o eth0 -p tcp --dport 443 -j ACCEPT
|
||||
iptables -A INPUT -i eth0 -p tcp --sport 443 -j ACCEPT
|
||||
iptables -A OUTPUT -o eth0 -j DROP
|
||||
iptables -A INPUT -i eth0 -j DROP
|
||||
|
||||
# IPv6: allow fips0 and loopback, block eth0
|
||||
ip6tables -A OUTPUT -o lo -j ACCEPT
|
||||
ip6tables -A INPUT -i lo -j ACCEPT
|
||||
ip6tables -A OUTPUT -o fips0 -j ACCEPT
|
||||
ip6tables -A INPUT -i fips0 -j ACCEPT
|
||||
ip6tables -A OUTPUT -o eth0 -j DROP
|
||||
ip6tables -A INPUT -i eth0 -j DROP
|
||||
|
||||
echo "iptables isolation rules applied"
|
||||
|
||||
# --- Start dnsmasq and launch FIPS ---
|
||||
|
||||
dnsmasq
|
||||
exec fips --config /etc/fips/fips.yaml
|
||||
49
examples/sidecar-nostr-relay/relay/config.toml
Normal file
49
examples/sidecar-nostr-relay/relay/config.toml
Normal file
@@ -0,0 +1,49 @@
|
||||
# nostr-rs-relay configuration for FIPS mesh deployment.
|
||||
# Full reference: https://github.com/scsibug/nostr-rs-relay
|
||||
|
||||
[info]
|
||||
# Shown in NIP-11 relay information document.
|
||||
relay_url = "" # Set to ws://[<your-fips-ipv6>]:8080 after first start
|
||||
name = "FIPS Nostr Relay"
|
||||
description = "A Nostr relay accessible over the FIPS mesh network."
|
||||
pubkey = "" # Optional: your npub (hex) as relay operator
|
||||
contact = "" # Optional: contact address
|
||||
|
||||
[database]
|
||||
# SQLite database path inside the container (mapped to relay-data volume).
|
||||
data_directory = "/usr/src/app/db"
|
||||
|
||||
[network]
|
||||
# Bind on all interfaces (IPv4 + IPv6) — FIPS delivers traffic via the fips0
|
||||
# TUN as IPv6 (fd00::/8). Using 0.0.0.0 with port 8080; the relay also needs
|
||||
# to accept IPv6 connections so we set port separately and rely on the OS
|
||||
# dual-stack socket (IPV6_V6ONLY=0).
|
||||
# nostr-rs-relay address field is just the IP, port is separate.
|
||||
address = "0.0.0.0"
|
||||
port = 8080
|
||||
ping_interval_seconds = 300
|
||||
|
||||
[limits]
|
||||
# Adjust to taste. These are conservative defaults for a personal relay.
|
||||
messages_per_sec = 10
|
||||
subscriptions_per_min = 20
|
||||
max_blocking_threads = 4
|
||||
max_event_bytes = 131072 # 128 KiB per event
|
||||
max_ws_message_bytes = 131072
|
||||
max_ws_frame_bytes = 131072
|
||||
broadcast_buffer = 16384
|
||||
event_persist_buffer = 4096
|
||||
|
||||
[authorization]
|
||||
# Set to true to require NIP-42 AUTH before accepting events.
|
||||
# Useful for a private relay — only authenticated users can publish.
|
||||
nip42_auth = false
|
||||
nip42_dms = false
|
||||
|
||||
[verified_users]
|
||||
# NIP-05 verification (optional).
|
||||
mode = "disabled"
|
||||
|
||||
[pay_to_relay]
|
||||
# Lightning payments for relay access (optional).
|
||||
enabled = false
|
||||
71
examples/sidecar-nostr-relay/relay/strfry.conf
Normal file
71
examples/sidecar-nostr-relay/relay/strfry.conf
Normal file
@@ -0,0 +1,71 @@
|
||||
##
|
||||
## strfry configuration for FIPS mesh deployment.
|
||||
## Full reference: https://github.com/hoytech/strfry
|
||||
##
|
||||
|
||||
db = "/usr/src/app/strfry-db/"
|
||||
|
||||
dbParams {
|
||||
# Maximum size of the database (bytes). 10 GiB is a safe default.
|
||||
mapsize = 10737418240
|
||||
}
|
||||
|
||||
relay {
|
||||
# Bind on all interfaces so the FIPS TUN (IPv4 + IPv6) can reach it.
|
||||
bind = "0.0.0.0"
|
||||
port = 7777
|
||||
|
||||
nofiles = 0
|
||||
|
||||
info {
|
||||
name = "FIPS Nostr Relay"
|
||||
description = "A Nostr relay accessible over the FIPS mesh network."
|
||||
pubkey = ""
|
||||
contact = ""
|
||||
}
|
||||
|
||||
# Maximum size of an inbound WebSocket message (bytes).
|
||||
maxWebsocketPayloadSize = 131072
|
||||
|
||||
# Send a ping every N seconds to keep connections alive.
|
||||
autoPingSeconds = 55
|
||||
|
||||
# Enable per-message compression.
|
||||
enableTcpNoDelay = false
|
||||
|
||||
rejectFutureEventsSeconds = 900
|
||||
rejectEphemeralEventsOlderThanSeconds = 60
|
||||
rejectEventsNewerThanSeconds = 900
|
||||
|
||||
maxFilterLimit = 500
|
||||
maxSubsPerConnection = 20
|
||||
|
||||
writePolicy {
|
||||
# Plugin executable for write-policy decisions (leave empty to allow all).
|
||||
plugin = ""
|
||||
}
|
||||
|
||||
compression {
|
||||
enabled = true
|
||||
slidingWindow = true
|
||||
}
|
||||
|
||||
logging {
|
||||
dumpInAll = false
|
||||
dumpInEvents = false
|
||||
dumpInReqs = false
|
||||
dbScanPerf = false
|
||||
}
|
||||
|
||||
numThreads {
|
||||
ingester = 3
|
||||
reqWorker = 3
|
||||
reqMonitor = 3
|
||||
negentropy = 2
|
||||
}
|
||||
|
||||
negentropy {
|
||||
enabled = true
|
||||
maxSyncEvents = 1000000
|
||||
}
|
||||
}
|
||||
1
examples/sidecar-nostr-relay/resolv.conf
Normal file
1
examples/sidecar-nostr-relay/resolv.conf
Normal file
@@ -0,0 +1 @@
|
||||
nameserver 127.0.0.1
|
||||
57
examples/sidecar-nostr-relay/scripts/build.sh
Executable file
57
examples/sidecar-nostr-relay/scripts/build.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Build the FIPS binary and Docker sidecar image.
|
||||
# Usage: ./build.sh
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
DOCKER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Find project root (directory containing Cargo.toml)
|
||||
PROJECT_ROOT="$(cd "$DOCKER_DIR/../.." && pwd)"
|
||||
if [ ! -f "$PROJECT_ROOT/Cargo.toml" ]; then
|
||||
echo "Error: Cannot find Cargo.toml at $PROJECT_ROOT" >&2
|
||||
echo "Expected layout: <project-root>/examples/sidecar-nostr-relay/scripts/build.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect host OS
|
||||
UNAME_S=$(uname -s)
|
||||
CARGO_TARGET="x86_64-unknown-linux-musl"
|
||||
|
||||
if [ "$UNAME_S" = "Darwin" ]; then
|
||||
echo "Detected macOS host — using cross-compilation for Linux..."
|
||||
|
||||
if ! command -v cargo-zigbuild &> /dev/null; then
|
||||
echo "Error: cargo-zigbuild not found." >&2
|
||||
echo "Please install it: cargo install cargo-zigbuild" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! rustup target list --installed | grep -q "$CARGO_TARGET"; then
|
||||
echo "Installing Rust target $CARGO_TARGET..."
|
||||
rustup target add "$CARGO_TARGET"
|
||||
fi
|
||||
|
||||
echo "Building FIPS for Linux (release) using cargo-zigbuild..."
|
||||
cargo zigbuild --release --target "$CARGO_TARGET" --manifest-path="$PROJECT_ROOT/Cargo.toml"
|
||||
|
||||
echo "Copying binaries to docker context..."
|
||||
cp "$PROJECT_ROOT/target/$CARGO_TARGET/release/fips" "$DOCKER_DIR/fips"
|
||||
cp "$PROJECT_ROOT/target/$CARGO_TARGET/release/fipsctl" "$DOCKER_DIR/fipsctl"
|
||||
cp "$PROJECT_ROOT/target/$CARGO_TARGET/release/fipstop" "$DOCKER_DIR/fipstop"
|
||||
else
|
||||
echo "Building FIPS (release)..."
|
||||
cargo build --release --manifest-path="$PROJECT_ROOT/Cargo.toml"
|
||||
|
||||
echo "Copying binaries to docker context..."
|
||||
cp "$PROJECT_ROOT/target/release/fips" "$DOCKER_DIR/fips"
|
||||
cp "$PROJECT_ROOT/target/release/fipsctl" "$DOCKER_DIR/fipsctl"
|
||||
cp "$PROJECT_ROOT/target/release/fipstop" "$DOCKER_DIR/fipstop"
|
||||
fi
|
||||
|
||||
echo "Done. Binaries at $DOCKER_DIR/{fips,fipsctl,fipstop}"
|
||||
echo ""
|
||||
echo "Building Docker image..."
|
||||
docker compose -f "$DOCKER_DIR/docker-compose.yml" build
|
||||
echo ""
|
||||
echo "Ready: cd examples/sidecar-nostr-relay && docker compose up -d"
|
||||
Reference in New Issue
Block a user