mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +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:
@@ -96,7 +96,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
PKG_VERSION: ${{ steps.version.outputs.version }}
|
PKG_VERSION: ${{ steps.version.outputs.version }}
|
||||||
LLVM_STRIP: llvm-strip
|
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
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fips
|
||||||
|
fipsctl
|
||||||
|
fipstop
|
||||||
@@ -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"]
|
||||||
@@ -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"]
|
||||||
@@ -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}
|
||||||
@@ -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:
|
||||||
Executable
+86
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
nameserver 127.0.0.1
|
||||||
+57
@@ -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