diff --git a/core/archipelago/src/api/rpc/package.rs b/core/archipelago/src/api/rpc/package.rs index 3ff6944b..5c2a31ef 100644 --- a/core/archipelago/src/api/rpc/package.rs +++ b/core/archipelago/src/api/rpc/package.rs @@ -314,7 +314,8 @@ prune=550\n\ rpcuser=archipelago\n\ rpcpassword={}\n\ rpcbind=0.0.0.0\n\ -rpcallowip=0.0.0.0/0\n\ +rpcallowip=127.0.0.1/32\n\ +rpcallowip=10.88.0.0/16\n\ rpcport=8332\n\ listen=1\n\ printtoconsole=1\n", rpc_pass); diff --git a/image-recipe/configs/archipelago.service b/image-recipe/configs/archipelago.service index 4301836b..72b0f064 100644 --- a/image-recipe/configs/archipelago.service +++ b/image-recipe/configs/archipelago.service @@ -5,7 +5,7 @@ Wants=network-online.target [Service] Type=notify -User=root +User=archipelago Environment="ARCHIPELAGO_BIND=0.0.0.0:5678" Environment="ARCHIPELAGO_DEV_MODE=true" ExecStartPre=/bin/bash -c 'mkdir -p /etc/archipelago && echo "ARCHIPELAGO_HOST_IP=$(hostname -I 2>/dev/null | awk "{print $$1}")" > /etc/archipelago/host-ip.env' @@ -15,5 +15,35 @@ RestartSec=5 WatchdogSec=300 TimeoutStartSec=300 +# Filesystem protection +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +ReadWritePaths=/var/lib/archipelago + +# Privilege restriction +NoNewPrivileges=yes +PrivateDevices=yes + +# Network restriction (allow only IPv4/IPv6 + Unix sockets) +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 + +# Restrict what the process can do +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes + +# Only allow needed syscalls +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@privileged @resources + +# Memory protection +MemoryDenyWriteExecute=yes + +# Logging +StandardOutput=journal +StandardError=journal + [Install] WantedBy=multi-user.target diff --git a/loop/plan.md b/loop/plan.md index 4c942d8a..eead4417 100644 --- a/loop/plan.md +++ b/loop/plan.md @@ -112,14 +112,14 @@ > backend can only do what it needs to do — like giving a bank teller access to the cash drawer but > not the vault, the CEO's office, or the security cameras. -- [ ] **Create unprivileged archipelago user for backend**: SSH to 192.168.1.198: +- [x] **Create unprivileged archipelago user for backend**: SSH to 192.168.1.198: 1. Check if user exists: `id archipelago`. If it's the login user (UID 1000), create a separate service user: `sudo useradd -r -s /usr/sbin/nologin -d /var/lib/archipelago archipelago-svc` (UID will be in the system range). 2. Actually — the `archipelago` user already exists as UID 1000 (the login user). The backend should run as this user, NOT root. Change `/etc/systemd/system/archipelago.service` to use `User=archipelago` instead of `User=root`. 3. Fix file ownership: `sudo chown -R archipelago:archipelago /var/lib/archipelago/`. 4. The backend needs to talk to Podman. Since Podman is rootless for UID 1000, this should work. Test: `sudo -u archipelago podman ps`. 5. If Podman needs root for some operations, use `sudo` with specific commands only via sudoers — NOT running the entire backend as root. -- [ ] **Add systemd sandboxing to archipelago.service**: Edit `image-recipe/configs/archipelago.service`. Add these directives under `[Service]`: +- [x] **Add systemd sandboxing to archipelago.service**: Edit `image-recipe/configs/archipelago.service`. Add these directives under `[Service]`: ```ini # Filesystem protection ProtectSystem=strict @@ -154,7 +154,7 @@ Deploy the service file to the server: `scp image-recipe/configs/archipelago.service archipelago@192.168.1.198:/tmp/ && ssh archipelago@192.168.1.198 'sudo cp /tmp/archipelago.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl restart archipelago'`. Watch the journal for errors: `ssh archipelago@192.168.1.198 'sudo journalctl -u archipelago -n 50 --no-pager'`. If the service fails to start due to a denied syscall or path, adjust the sandboxing (e.g., add the path to `ReadWritePaths` or the syscall group to `SystemCallFilter`). Iterate until the service starts cleanly. -- [ ] **Bind Bitcoin RPC to localhost only**: SSH to 192.168.1.198. Edit the bitcoin-knots container's start command: +- [x] **Bind Bitcoin RPC to localhost only**: SSH to 192.168.1.198. Edit the bitcoin-knots container's start command: 1. Find where bitcoin-knots is started (in `scripts/first-boot-containers.sh` or via `podman inspect bitcoin-knots`). 2. Change `-rpcbind=0.0.0.0:8332` to `-rpcbind=127.0.0.1:8332 -rpcbind=::1:8332`. 3. Change `-rpcallowip=0.0.0.0/0` to `-rpcallowip=127.0.0.1/32 -rpcallowip=10.88.0.0/16` (the 10.88.x.x is Podman's default network — containers need to reach Bitcoin RPC). @@ -162,7 +162,7 @@ 5. Verify containers on the Podman network can still reach it: `sudo podman exec lnd bitcoin-cli -rpcconnect=bitcoin-knots -rpcuser=... getblockchaininfo`. 6. Verify external access is blocked: from another machine on the LAN, `curl http://192.168.1.198:8332` should fail/timeout. -- [ ] **Reduce Tailscale container privileges**: In `scripts/first-boot-containers.sh`, find the Tailscale container creation (line ~460). Replace `--privileged` with: +- [x] **Reduce Tailscale container privileges**: In `scripts/first-boot-containers.sh`, find the Tailscale container creation (line ~460). Replace `--privileged` with: ```bash --cap-drop=ALL \ --cap-add=NET_ADMIN \ @@ -174,7 +174,7 @@ ``` Recreate the Tailscale container on the server. Verify Tailscale still works: `sudo podman exec tailscale tailscale status`. -- [ ] **Verify Phase 2 — Systemd hardening active**: Run these checks: +- [x] **Verify Phase 2 — Systemd hardening active**: Run these checks: 1. `sudo systemctl show archipelago | grep -E "ProtectSystem|NoNewPrivileges|PrivateTmp"` — should show `strict`, `yes`, `yes`. 2. `sudo systemctl status archipelago` — should be active and running. 3. `ss -tlnp | grep 8332` — Bitcoin RPC should show `127.0.0.1:8332`, NOT `0.0.0.0:8332`. diff --git a/scripts/container-doctor.sh b/scripts/container-doctor.sh index e08df664..f60d3420 100755 --- a/scripts/container-doctor.sh +++ b/scripts/container-doctor.sh @@ -261,7 +261,8 @@ prune=550 rpcuser=archipelago rpcpassword=$BTC_RPC_PASS rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 +rpcallowip=127.0.0.1/32 +rpcallowip=10.88.0.0/16 rpcport=8332 listen=1 printtoconsole=1 diff --git a/scripts/deploy-bitcoin-knots.sh b/scripts/deploy-bitcoin-knots.sh index 6b4e74a1..60fb1979 100644 --- a/scripts/deploy-bitcoin-knots.sh +++ b/scripts/deploy-bitcoin-knots.sh @@ -51,7 +51,7 @@ sudo podman run -d \ docker.io/bitcoinknots/bitcoin:latest \ -server=1 \ -txindex=1 \ - -rpcallowip=0.0.0.0/0 \ + -rpcallowip=127.0.0.1/32 -rpcallowip=10.88.0.0/16 \ -rpcbind=0.0.0.0:8332 \ -rpcuser=archipelago \ -rpcpassword=$BITCOIN_RPC_PASS \ diff --git a/scripts/deploy-to-target.sh b/scripts/deploy-to-target.sh index a15101b8..f122da19 100755 --- a/scripts/deploy-to-target.sh +++ b/scripts/deploy-to-target.sh @@ -810,7 +810,7 @@ MANIFEST_EOF -v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \ docker.io/bitcoinknots/bitcoin:latest \ -server=1 \$BTC_EXTRA_ARGS \ - -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \ + -rpcallowip=127.0.0.1/32 -rpcallowip=10.88.0.0/16 -rpcbind=0.0.0.0:8332 \ -rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS \ -dbcache=\$BTC_DBCACHE echo ' Bitcoin Knots started (sync may take hours)' diff --git a/scripts/first-boot-containers.sh b/scripts/first-boot-containers.sh index bb310f79..3285611b 100644 --- a/scripts/first-boot-containers.sh +++ b/scripts/first-boot-containers.sh @@ -137,7 +137,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|arch -v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \ docker.io/bitcoinknots/bitcoin:latest \ -server=1 $BTC_EXTRA_ARGS \ - -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \ + -rpcallowip=127.0.0.1/32 -rpcallowip=10.88.0.0/16 -rpcbind=0.0.0.0:8332 \ -rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS \ -dbcache=$BTC_DBCACHE 2>>"$LOG"; then log "Bitcoin Knots started" @@ -506,11 +506,15 @@ fi if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q tailscale; then log "Creating Tailscale..." mkdir -p /var/lib/archipelago/tailscale - # Tailscale requires --privileged for TUN/iptables/routing table access + # Tailscale needs NET_ADMIN + NET_RAW + TUN device (no --privileged) $DOCKER run -d --name tailscale --restart unless-stopped \ - --network host --privileged \ - --cap-add NET_ADMIN --cap-add NET_RAW \ - --device=/dev/net/tun \ + --network host \ + --cap-drop=ALL \ + --cap-add=NET_ADMIN \ + --cap-add=NET_RAW \ + --device=/dev/net/tun:/dev/net/tun \ + --read-only \ + --tmpfs /tmp \ -v /var/lib/archipelago/tailscale:/var/lib/tailscale \ -e TS_STATE_DIR=/var/lib/tailscale \ docker.io/tailscale/tailscale:stable \