- README.md: full project overview with features, install extras, CLI reference, web UI routes, config table, architecture diagrams, security model, /health API, and development setup - CLAUDE.md: updated for monorepo — reflects inlined subpackages, new import patterns, pip extras, and added modules - docs/deployment.md: practical RPi deployment guide covering hardware, OS setup, security hardening (swap/coredumps/firewall), installation, systemd service, config reference, fieldkit setup, key management, operational security limitations, troubleshooting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
639 lines
17 KiB
Markdown
639 lines
17 KiB
Markdown
# SooSeF Deployment Guide
|
|
|
|
Deploying SooSeF on a Raspberry Pi for airgapped LAN use.
|
|
|
|
This guide is for field deployers: IT staff at NGOs, technically competent journalists,
|
|
and anyone setting up a shared SooSeF instance on a local network with no internet access.
|
|
|
|
---
|
|
|
|
## 1. Hardware Requirements
|
|
|
|
**Minimum:**
|
|
|
|
- Raspberry Pi 4 Model B, 2 GB RAM (4 GB recommended)
|
|
- 32 GB microSD card (Class 10 / A2 recommended)
|
|
- USB-C power supply (5V 3A, official RPi PSU recommended)
|
|
- Ethernet cable or pre-configured Wi-Fi for LAN
|
|
|
|
**Optional:**
|
|
|
|
- USB GPS module (e.g., VK-162) for geofencing
|
|
- Momentary push button + 10k pull-down resistor on GPIO pin 17 for hardware killswitch
|
|
- USB drive for airgap package transfer and key backups
|
|
- Case with passive cooling (no fan noise in sensitive environments)
|
|
|
|
If you plan to use the hardware killswitch button, wire it between GPIO 17 and 3.3V with
|
|
a 10k pull-down to GND. The default requires a 5-second hold to trigger (configurable via
|
|
`gpio_killswitch_hold_seconds` in config).
|
|
|
|
---
|
|
|
|
## 2. OS Setup
|
|
|
|
Install **Raspberry Pi OS Lite (64-bit, Bookworm or later)**. The desktop environment is
|
|
unnecessary and wastes resources.
|
|
|
|
After first boot:
|
|
|
|
```bash
|
|
# Set hostname
|
|
sudo hostnamectl set-hostname soosef-node
|
|
|
|
# Create a dedicated service user
|
|
sudo useradd -m -s /bin/bash soosef
|
|
sudo passwd soosef
|
|
|
|
# Enable SSH (if not already)
|
|
sudo systemctl enable --now ssh
|
|
|
|
# Update system packages (do this before going airgapped)
|
|
sudo apt update && sudo apt upgrade -y
|
|
```
|
|
|
|
Set a strong password. If possible, use SSH key authentication and disable password login
|
|
in `/etc/ssh/sshd_config` (`PasswordAuthentication no`).
|
|
|
|
---
|
|
|
|
## 3. Security Hardening
|
|
|
|
### 3.1 Disable or encrypt swap
|
|
|
|
Python does not zero memory when objects are garbage-collected. Cryptographic keys,
|
|
passwords, and plaintext can persist in swap long after the process exits. On an SD card,
|
|
this data may survive even after the swap file is deleted (wear leveling).
|
|
|
|
**Option A: Disable swap entirely (recommended if you have 4 GB+ RAM)**
|
|
|
|
```bash
|
|
sudo dphys-swapfile swapoff
|
|
sudo systemctl disable dphys-swapfile
|
|
sudo rm /var/swap
|
|
```
|
|
|
|
**Option B: Encrypted swap (if you need swap on 2 GB models)**
|
|
|
|
```bash
|
|
sudo dphys-swapfile swapoff
|
|
sudo systemctl disable dphys-swapfile
|
|
|
|
# Use dm-crypt with a random key (regenerated each boot)
|
|
echo "swap /dev/mmcblk0p3 /dev/urandom swap,cipher=aes-xts-plain64,size=256" | sudo tee -a /etc/crypttab
|
|
echo "/dev/mapper/swap none swap sw 0 0" | sudo tee -a /etc/fstab
|
|
```
|
|
|
|
Adjust the device path to match your partition layout.
|
|
|
|
### 3.2 Disable core dumps
|
|
|
|
A core dump from the SooSeF process would contain key material in plaintext.
|
|
|
|
```bash
|
|
echo "* hard core 0" | sudo tee -a /etc/security/limits.conf
|
|
echo "kernel.core_pattern=/dev/null" | sudo tee -a /etc/sysctl.d/99-soosef.conf
|
|
sudo sysctl -p /etc/sysctl.d/99-soosef.conf
|
|
```
|
|
|
|
### 3.3 Firewall
|
|
|
|
```bash
|
|
sudo apt install -y ufw
|
|
sudo ufw default deny incoming
|
|
sudo ufw default allow outgoing
|
|
sudo ufw allow ssh
|
|
sudo ufw allow 5000/tcp # SooSeF web UI
|
|
sudo ufw enable
|
|
```
|
|
|
|
If running fully airgapped, also deny outgoing:
|
|
|
|
```bash
|
|
sudo ufw default deny outgoing
|
|
```
|
|
|
|
### 3.4 Disable unnecessary services
|
|
|
|
```bash
|
|
sudo systemctl disable bluetooth
|
|
sudo systemctl disable avahi-daemon
|
|
sudo systemctl disable triggerhappy
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Installation
|
|
|
|
### 4.1 System dependencies
|
|
|
|
```bash
|
|
sudo apt install -y \
|
|
python3 python3-pip python3-venv python3-dev \
|
|
libjpeg-dev libjpeg62-turbo-dev zlib1g-dev \
|
|
libffi-dev libssl-dev \
|
|
shred coreutils
|
|
```
|
|
|
|
`libjpeg-dev` is required for Pillow and jpeglib (DCT steganography). `libffi-dev` is
|
|
required for argon2-cffi.
|
|
|
|
### 4.2 Create virtual environment
|
|
|
|
```bash
|
|
sudo -u soosef -i # Switch to the soosef user
|
|
|
|
python3 -m venv ~/soosef-env
|
|
source ~/soosef-env/bin/activate
|
|
pip install --upgrade pip wheel
|
|
```
|
|
|
|
### 4.3 Install SooSeF
|
|
|
|
**If the Pi has internet access (pre-deployment):**
|
|
|
|
```bash
|
|
pip install "soosef[web,cli,fieldkit]"
|
|
```
|
|
|
|
For hardware killswitch support via GPIO:
|
|
|
|
```bash
|
|
pip install "soosef[rpi]"
|
|
```
|
|
|
|
The `rpi` extra includes `web`, `cli`, `fieldkit`, and `gpiozero`.
|
|
|
|
**For airgapped install (no internet on the Pi):**
|
|
|
|
On an internet-connected machine with the same architecture (aarch64 for RPi 4):
|
|
|
|
```bash
|
|
mkdir soosef-wheels
|
|
pip download "soosef[rpi]" -d soosef-wheels/
|
|
```
|
|
|
|
Copy the `soosef-wheels/` directory to a USB drive, then on the Pi:
|
|
|
|
```bash
|
|
pip install --no-index --find-links /media/usb/soosef-wheels/ "soosef[rpi]"
|
|
```
|
|
|
|
### 4.4 Verify installation
|
|
|
|
```bash
|
|
soosef --version
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Initial Setup
|
|
|
|
### 5.1 Initialize SooSeF
|
|
|
|
```bash
|
|
soosef init
|
|
```
|
|
|
|
This creates the `~/.soosef/` directory structure:
|
|
|
|
```
|
|
~/.soosef/
|
|
config.json Unified configuration
|
|
identity/ Ed25519 signing keypair (verisoo)
|
|
private.pem
|
|
public.pem
|
|
identity.meta.json
|
|
stegasoo/ Stegasoo state
|
|
channel.key AES-256-GCM channel key
|
|
attestations/ Verisoo attestation log and index
|
|
chain/ Hash chain data
|
|
auth/ Web UI user database (SQLite)
|
|
certs/ Self-signed TLS certificates
|
|
fieldkit/ Killswitch, deadman, tamper, USB, geofence state
|
|
temp/ Ephemeral upload/processing files
|
|
instance/ Flask session data
|
|
audit.jsonl Append-only audit trail
|
|
```
|
|
|
|
The `identity/` and `auth/` directories are created with mode 0700.
|
|
|
|
`soosef init` generates:
|
|
|
|
- An Ed25519 identity keypair (for signing attestations)
|
|
- A channel key (for steganographic encoding)
|
|
- A default `config.json`
|
|
|
|
### 5.2 First user setup
|
|
|
|
Start the server and create the first admin user through the web UI:
|
|
|
|
```bash
|
|
soosef serve --host 0.0.0.0 --no-https
|
|
```
|
|
|
|
Navigate to `http://<pi-ip>:5000` from a device on the same LAN. The web UI will prompt
|
|
you to create the first user account.
|
|
|
|
After creating the admin account, stop the server (Ctrl+C) and restart with HTTPS
|
|
(see Section 6).
|
|
|
|
---
|
|
|
|
## 6. Running
|
|
|
|
### 6.1 Basic usage
|
|
|
|
```bash
|
|
# LAN-only, no HTTPS (acceptable if the network is physically isolated)
|
|
soosef serve --host 0.0.0.0 --no-https
|
|
|
|
# With self-signed HTTPS (recommended)
|
|
soosef serve --host 0.0.0.0
|
|
|
|
# Custom port
|
|
soosef serve --host 0.0.0.0 --port 8443
|
|
```
|
|
|
|
On first HTTPS start, SooSeF auto-generates a self-signed certificate at
|
|
`~/.soosef/certs/cert.pem`. Browsers will show a certificate warning -- this is expected
|
|
for self-signed certs. Instruct users to accept the warning or distribute the cert file
|
|
to client devices.
|
|
|
|
SooSeF uses Waitress (pure Python, no C dependencies) as its production server with 4
|
|
worker threads by default. Adjust with `--workers`.
|
|
|
|
### 6.2 systemd service
|
|
|
|
Create `/etc/systemd/system/soosef.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=SooSeF Security Fieldkit
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=soosef
|
|
Group=soosef
|
|
WorkingDirectory=/home/soosef
|
|
Environment="PATH=/home/soosef/soosef-env/bin:/usr/bin"
|
|
ExecStart=/home/soosef/soosef-env/bin/soosef serve --host 0.0.0.0 --workers 4
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
# Hardening
|
|
NoNewPrivileges=yes
|
|
ProtectSystem=strict
|
|
ProtectHome=read-only
|
|
ReadWritePaths=/home/soosef/.soosef
|
|
PrivateTmp=yes
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Enable and start:
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now soosef
|
|
sudo journalctl -u soosef -f # Watch logs
|
|
```
|
|
|
|
Add `--no-https` to `ExecStart` if running on a physically isolated LAN where TLS is
|
|
unnecessary.
|
|
|
|
---
|
|
|
|
## 7. Configuration
|
|
|
|
Configuration lives at `~/.soosef/config.json`. Edit it directly or use the web admin
|
|
panel. All fields have sensible defaults -- you only need to set what you want to change.
|
|
|
|
| Field | Default | Description |
|
|
|---|---|---|
|
|
| `host` | `127.0.0.1` | Bind address. Set to `0.0.0.0` for LAN access. |
|
|
| `port` | `5000` | TCP port for the web UI. |
|
|
| `https_enabled` | `true` | Enable self-signed HTTPS. |
|
|
| `auth_enabled` | `true` | Require login. Do not disable this. |
|
|
| `max_upload_mb` | `50` | Maximum file upload size in MB. |
|
|
| `session_timeout_minutes` | `15` | Idle session expiry. Lower is safer. |
|
|
| `login_lockout_attempts` | `5` | Failed logins before lockout. |
|
|
| `login_lockout_minutes` | `15` | Lockout duration. |
|
|
| `killswitch_enabled` | `false` | Enable software killswitch. |
|
|
| `deadman_enabled` | `false` | Enable dead man's switch. |
|
|
| `deadman_interval_hours` | `24` | Hours between required check-ins. |
|
|
| `deadman_grace_hours` | `2` | Grace period after missed check-in before purge. |
|
|
| `deadman_warning_webhook` | `""` | URL to POST a JSON warning when check-in is overdue. Must be a public URL (SSRF protection blocks private IPs). |
|
|
| `usb_monitoring_enabled` | `false` | Monitor for unauthorized USB devices. |
|
|
| `tamper_monitoring_enabled` | `false` | File integrity monitoring. |
|
|
| `chain_enabled` | `true` | Wrap attestations in a hash chain. |
|
|
| `chain_auto_wrap` | `true` | Automatically chain verisoo attestations. |
|
|
| `backup_reminder_days` | `7` | Warn if no backup in this many days. |
|
|
| `gpio_killswitch_pin` | `17` | GPIO pin for hardware killswitch button. |
|
|
| `gpio_killswitch_hold_seconds` | `5.0` | Required hold time to trigger hardware killswitch. |
|
|
|
|
Example minimal config for a field deployment:
|
|
|
|
```json
|
|
{
|
|
"host": "0.0.0.0",
|
|
"port": 5000,
|
|
"https_enabled": true,
|
|
"session_timeout_minutes": 10,
|
|
"deadman_enabled": true,
|
|
"deadman_interval_hours": 12,
|
|
"deadman_grace_hours": 1,
|
|
"killswitch_enabled": true,
|
|
"backup_reminder_days": 3
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Fieldkit Setup
|
|
|
|
### 8.1 Dead man's switch
|
|
|
|
The dead man's switch requires periodic check-ins. If you miss a check-in, SooSeF sends
|
|
a warning during the grace period. If the grace period expires without a check-in, the
|
|
killswitch fires automatically and destroys all key material and data.
|
|
|
|
Arm it:
|
|
|
|
```bash
|
|
soosef fieldkit deadman arm --interval 12 --grace 1
|
|
```
|
|
|
|
This requires a check-in every 12 hours, with a 1-hour grace period.
|
|
|
|
Check in:
|
|
|
|
```bash
|
|
soosef fieldkit checkin
|
|
```
|
|
|
|
You can also check in through the web UI at `/fieldkit`.
|
|
|
|
Check status:
|
|
|
|
```bash
|
|
soosef status
|
|
```
|
|
|
|
The dead man's switch enforcement loop runs as a background thread inside `soosef serve`,
|
|
checking every 60 seconds. It will send a webhook warning (if configured) during the grace
|
|
period, then execute a full purge if the grace period expires.
|
|
|
|
Disarm:
|
|
|
|
```bash
|
|
soosef fieldkit deadman disarm
|
|
```
|
|
|
|
### 8.2 Geofence
|
|
|
|
If you have a USB GPS module, you can set a geographic boundary. SooSeF will trigger the
|
|
killswitch if the device moves outside the fence.
|
|
|
|
```bash
|
|
soosef fieldkit geofence set --lat 50.4501 --lon 30.5234 --radius 5000
|
|
```
|
|
|
|
Coordinates are in decimal degrees, radius in meters.
|
|
|
|
### 8.3 USB whitelist
|
|
|
|
Record currently connected USB devices as the trusted baseline:
|
|
|
|
```bash
|
|
soosef fieldkit usb learn
|
|
```
|
|
|
|
When monitoring is enabled, SooSeF will alert (or trigger killswitch, depending on config)
|
|
if an unknown USB device is connected.
|
|
|
|
### 8.4 Tamper baseline
|
|
|
|
Record file integrity baselines for critical files:
|
|
|
|
```bash
|
|
soosef fieldkit tamper baseline
|
|
```
|
|
|
|
SooSeF monitors for unexpected changes to tracked files when tamper monitoring is enabled.
|
|
|
|
---
|
|
|
|
## 9. Key Management
|
|
|
|
SooSeF manages two separate key domains:
|
|
|
|
- **Ed25519 identity key** (`~/.soosef/identity/`) -- used for signing attestations.
|
|
This is your provenance identity.
|
|
- **AES-256-GCM channel key** (`~/.soosef/stegasoo/channel.key`) -- used for
|
|
steganographic encoding/decoding. Shared with anyone who needs to read your
|
|
stego messages.
|
|
|
|
These are separate security concerns and are never merged.
|
|
|
|
### 9.1 Backup
|
|
|
|
Back up keys regularly. SooSeF warns if no backup has been taken within the
|
|
`backup_reminder_days` window (default: 7 days).
|
|
|
|
```bash
|
|
soosef keys backup --output /media/usb/soosef-backup.enc
|
|
```
|
|
|
|
This creates an encrypted bundle. You will be prompted for a passphrase. Store the USB
|
|
drive physically separate from the Pi.
|
|
|
|
### 9.2 Restore
|
|
|
|
```bash
|
|
soosef keys restore --input /media/usb/soosef-backup.enc
|
|
```
|
|
|
|
### 9.3 Key rotation
|
|
|
|
Generate a new channel key (the old one is overwritten):
|
|
|
|
```bash
|
|
soosef init --no-identity
|
|
```
|
|
|
|
Generate a new identity (the old one is overwritten -- all previous attestations will
|
|
reference the old fingerprint):
|
|
|
|
```bash
|
|
soosef init --no-channel
|
|
```
|
|
|
|
After rotating keys, take a fresh backup immediately.
|
|
|
|
### 9.4 Trusting collaborator keys
|
|
|
|
Import a collaborator's public key so you can verify their attestations:
|
|
|
|
```bash
|
|
soosef keys trust --import /media/usb/collaborator-pubkey.pem
|
|
```
|
|
|
|
Verify the fingerprint out-of-band (in person, over a secure channel) before trusting.
|
|
|
|
---
|
|
|
|
## 10. Operational Security Notes
|
|
|
|
SooSeF is a tool, not a shield. Understand what it cannot do.
|
|
|
|
### What SooSeF does not protect against
|
|
|
|
- **Physical coercion.** If someone forces you to unlock the device or reveal passwords,
|
|
no software can help. The killswitch is for situations where you can act before
|
|
interception, not during.
|
|
- **Social engineering.** SooSeF cannot prevent users from being tricked into revealing
|
|
credentials or disabling security features.
|
|
- **Leaving the browser open.** The session timeout helps, but if someone walks up to an
|
|
unlocked browser session, they have access. Train users to close the browser or lock the
|
|
screen.
|
|
- **Compromised client devices.** SooSeF secures the server. If a user's laptop has
|
|
malware, their browser session is compromised regardless of what the server does.
|
|
|
|
### Shred limitations on flash storage
|
|
|
|
The killswitch uses `shred` on Linux (3-pass overwrite + zero). On spinning disks, this
|
|
is effective. On SD cards and SSDs, **it is not reliable** because:
|
|
|
|
- Flash translation layers remap physical blocks. Overwritten data may persist on
|
|
remapped blocks.
|
|
- Wear leveling distributes writes across the flash, meaning the original block may be
|
|
preserved.
|
|
|
|
SooSeF's defense against this is **cryptographic erasure**: destroy the keys first, then
|
|
the data. Even if fragments of encrypted data survive on flash, they are useless without
|
|
the keys. The killswitch destroys keys before anything else, and keys are small enough to
|
|
fit in a single flash block.
|
|
|
|
This is why full-disk encryption matters -- see below.
|
|
|
|
### Full-disk encryption (LUKS)
|
|
|
|
For serious deployments, encrypt the entire SD card (or at least the data partition) with
|
|
LUKS. This way, even if the SD card is physically seized:
|
|
|
|
- All data at rest is encrypted
|
|
- Shred limitations are irrelevant because the underlying storage is encrypted
|
|
- Power-off = data inaccessible (assuming the LUKS passphrase is strong)
|
|
|
|
Setting up LUKS on Raspberry Pi OS is beyond the scope of this guide, but the short
|
|
version is: create an encrypted partition, mount it at `/home/soosef/.soosef`, and
|
|
configure auto-unlock via a keyfile on a separate USB (remove the USB after boot in
|
|
hostile environments).
|
|
|
|
### Memory considerations
|
|
|
|
Python does not securely zero memory. Key material, passwords, and plaintext may persist
|
|
in process memory and swap. The mitigations in Section 3 (disable swap, disable core
|
|
dumps) reduce the window, but a memory dump of the running process would expose secrets.
|
|
This is a fundamental limitation of Python-based security tools.
|
|
|
|
---
|
|
|
|
## 11. Troubleshooting
|
|
|
|
### Health check
|
|
|
|
SooSeF exposes a `/health` endpoint on the web UI. Hit it from the LAN to verify the
|
|
server is running:
|
|
|
|
```bash
|
|
curl -k https://<pi-ip>:5000/health
|
|
```
|
|
|
|
The `-k` flag skips certificate verification for self-signed certs.
|
|
|
|
### System status
|
|
|
|
```bash
|
|
soosef status
|
|
```
|
|
|
|
This checks identity key, channel key, trusted keys, dead man's switch state, geofence,
|
|
chain status, and backup status.
|
|
|
|
### Common issues
|
|
|
|
**scipy fails to build on Raspberry Pi**
|
|
|
|
scipy requires Fortran and BLAS libraries. On Raspberry Pi OS:
|
|
|
|
```bash
|
|
sudo apt install -y gfortran libopenblas-dev
|
|
```
|
|
|
|
If the build still fails (common on low-memory Pis), increase swap temporarily during
|
|
install, then disable it again:
|
|
|
|
```bash
|
|
sudo dphys-swapfile swapon
|
|
pip install scipy
|
|
sudo dphys-swapfile swapoff
|
|
```
|
|
|
|
Or use pre-built wheels:
|
|
|
|
```bash
|
|
pip install --prefer-binary scipy
|
|
```
|
|
|
|
**Argon2 OOM on low-memory Pi (2 GB)**
|
|
|
|
Argon2 password hashing is memory-intensive by design. If the server crashes during login
|
|
on a 2 GB Pi with other services running, either:
|
|
|
|
- Free memory by disabling unnecessary services
|
|
- Reduce Argon2 memory cost (not recommended -- weakens password hashing)
|
|
|
|
The default Argon2 parameters use ~64 MB per hash operation. With 2 GB total RAM and a
|
|
running server, this is tight but workable if nothing else is competing for memory.
|
|
|
|
**Web UI unreachable from LAN**
|
|
|
|
1. Check that `host` is set to `0.0.0.0` in config, not `127.0.0.1`
|
|
2. Check firewall: `sudo ufw status` -- port 5000 must be allowed
|
|
3. Check the service is running: `sudo systemctl status soosef`
|
|
4. Check the Pi's IP: `ip addr show`
|
|
|
|
**Certificate warnings in browser**
|
|
|
|
Expected with self-signed certificates. Users must click through the warning. To avoid
|
|
this, distribute `~/.soosef/certs/cert.pem` to client devices and install it as a
|
|
trusted certificate.
|
|
|
|
**Dead man's switch fires unexpectedly**
|
|
|
|
The switch checks every 60 seconds. If the Pi loses power or the service crashes and
|
|
does not restart within `interval_hours + grace_hours`, the switch will fire on the next
|
|
start. Make sure the systemd service is set to `Restart=on-failure` and the Pi has
|
|
reliable power.
|
|
|
|
If you need to perform maintenance, disarm the switch first:
|
|
|
|
```bash
|
|
soosef fieldkit deadman disarm
|
|
```
|
|
|
|
Re-arm when maintenance is complete.
|
|
|
|
**Permission errors on ~/.soosef/**
|
|
|
|
The `identity/`, `auth/`, and `certs/` directories are mode 0700. If running under a
|
|
different user than the one who ran `soosef init`, you will get permission denied errors.
|
|
Always run SooSeF as the same user.
|
|
|
|
```bash
|
|
ls -la ~/.soosef/
|
|
```
|