diff --git a/deploy/config-presets/README.md b/deploy/config-presets/README.md new file mode 100644 index 0000000..24f9c67 --- /dev/null +++ b/deploy/config-presets/README.md @@ -0,0 +1,46 @@ +# SooSeF Threat Level Configuration Presets + +Select a preset based on your operational environment. Copy the appropriate +JSON file to `~/.soosef/config.json` (or let the setup wizard choose one). + +## Presets + +### low-threat.json — Press Freedom Country +Nordics, New Zealand, Canada. Risk is accidental data loss, not adversarial seizure. +- No killswitch or dead man's switch +- Relaxed session timeouts (30 min) +- Backup reminders every 14 days +- Chain enabled for provenance integrity + +### medium-threat.json — Restricted Press +Turkey, Hungary, India. Risk of legal pressure, device seizure at borders. +- Killswitch available, dead man's switch at 48h/4h grace +- USB monitoring enabled +- Cover name: "Office Document Manager" +- Backup reminders every 7 days + +### high-threat.json — Active Conflict Zone +Syria, Myanmar, Ethiopia, Iran. Risk of raids, equipment seizure, physical coercion. +- 5-minute session timeout +- Dead man's switch at 12h/1h grace +- Tamper monitoring enabled +- Cover name: "Local Inventory Tracker" +- Daily backup reminders + +### critical-threat.json — Targeted Surveillance +Specific journalist or org targeted by state actor (Pegasus-level). +- Web UI bound to 127.0.0.1 only (access via SSH tunnel) +- 3-minute session timeout +- Dead man's switch at 6h/1h grace +- Cover name: "System Statistics" +- All monitoring enabled +- Consider: full-disk encryption, remove SSH after setup, Tor hidden service + +## Usage + +```bash +# Copy preset to config location +cp deploy/config-presets/high-threat.json ~/.soosef/config.json + +# Or via CLI (future: soosef init --threat-level high) +``` diff --git a/deploy/config-presets/critical-threat.json b/deploy/config-presets/critical-threat.json new file mode 100644 index 0000000..008870f --- /dev/null +++ b/deploy/config-presets/critical-threat.json @@ -0,0 +1,20 @@ +{ + "host": "127.0.0.1", + "port": 5000, + "https_enabled": true, + "auth_enabled": true, + "session_timeout_minutes": 3, + "login_lockout_attempts": 3, + "login_lockout_minutes": 60, + "killswitch_enabled": true, + "deadman_enabled": true, + "deadman_interval_hours": 6, + "deadman_grace_hours": 1, + "deadman_warning_webhook": "", + "usb_monitoring_enabled": true, + "tamper_monitoring_enabled": true, + "chain_enabled": true, + "chain_auto_wrap": true, + "backup_reminder_days": 1, + "cover_name": "System Statistics" +} diff --git a/deploy/config-presets/high-threat.json b/deploy/config-presets/high-threat.json new file mode 100644 index 0000000..61d14cd --- /dev/null +++ b/deploy/config-presets/high-threat.json @@ -0,0 +1,20 @@ +{ + "host": "0.0.0.0", + "port": 5000, + "https_enabled": true, + "auth_enabled": true, + "session_timeout_minutes": 5, + "login_lockout_attempts": 3, + "login_lockout_minutes": 30, + "killswitch_enabled": true, + "deadman_enabled": true, + "deadman_interval_hours": 12, + "deadman_grace_hours": 1, + "deadman_warning_webhook": "", + "usb_monitoring_enabled": true, + "tamper_monitoring_enabled": true, + "chain_enabled": true, + "chain_auto_wrap": true, + "backup_reminder_days": 1, + "cover_name": "Local Inventory Tracker" +} diff --git a/deploy/config-presets/low-threat.json b/deploy/config-presets/low-threat.json new file mode 100644 index 0000000..94614ec --- /dev/null +++ b/deploy/config-presets/low-threat.json @@ -0,0 +1,20 @@ +{ + "host": "0.0.0.0", + "port": 5000, + "https_enabled": true, + "auth_enabled": true, + "session_timeout_minutes": 30, + "login_lockout_attempts": 10, + "login_lockout_minutes": 5, + "killswitch_enabled": false, + "deadman_enabled": false, + "deadman_interval_hours": 24, + "deadman_grace_hours": 2, + "deadman_warning_webhook": "", + "usb_monitoring_enabled": false, + "tamper_monitoring_enabled": false, + "chain_enabled": true, + "chain_auto_wrap": true, + "backup_reminder_days": 14, + "cover_name": "" +} diff --git a/deploy/config-presets/medium-threat.json b/deploy/config-presets/medium-threat.json new file mode 100644 index 0000000..2ac733f --- /dev/null +++ b/deploy/config-presets/medium-threat.json @@ -0,0 +1,20 @@ +{ + "host": "0.0.0.0", + "port": 5000, + "https_enabled": true, + "auth_enabled": true, + "session_timeout_minutes": 15, + "login_lockout_attempts": 5, + "login_lockout_minutes": 15, + "killswitch_enabled": true, + "deadman_enabled": true, + "deadman_interval_hours": 48, + "deadman_grace_hours": 4, + "deadman_warning_webhook": "", + "usb_monitoring_enabled": true, + "tamper_monitoring_enabled": false, + "chain_enabled": true, + "chain_auto_wrap": true, + "backup_reminder_days": 7, + "cover_name": "Office Document Manager" +} diff --git a/deploy/docker/.dockerignore b/deploy/docker/.dockerignore new file mode 100644 index 0000000..6da019e --- /dev/null +++ b/deploy/docker/.dockerignore @@ -0,0 +1,10 @@ +.git +.claude +__pycache__ +*.pyc +.coverage +.pytest_cache +docs +deploy/live-usb +test_data +*.egg-info diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile new file mode 100644 index 0000000..196c4d6 --- /dev/null +++ b/deploy/docker/Dockerfile @@ -0,0 +1,80 @@ +# SooSeF Federation Server +# Multi-stage build for minimal image size. +# +# Tier 2: Org server (full features — web UI, attestation, federation, stego) +# docker build -t soosef-server . +# docker run -v soosef-data:/data -p 5000:5000 -p 8000:8000 soosef-server +# +# Tier 3: Federation relay (attestation + federation only, no stego, no web UI) +# docker build --target relay -t soosef-relay . +# docker run -v relay-data:/data -p 8000:8000 soosef-relay + +# === Stage 1: Build dependencies === +FROM python:3.12-slim-bookworm AS builder + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc g++ gfortran \ + libjpeg62-turbo-dev zlib1g-dev libffi-dev libssl-dev \ + libopenblas-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /build +COPY . . + +# Install into a virtual environment for clean copying +RUN python -m venv /opt/soosef-env \ + && /opt/soosef-env/bin/pip install --no-cache-dir \ + ".[web,cli,attest,stego-dct,api,federation]" + +# === Stage 2: Federation relay (minimal) === +FROM python:3.12-slim-bookworm AS relay + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libjpeg62-turbo libopenblas0 \ + && rm -rf /var/lib/apt/lists/* \ + && useradd -m -s /bin/bash soosef + +COPY --from=builder /opt/soosef-env /opt/soosef-env + +ENV PATH="/opt/soosef-env/bin:$PATH" \ + SOOSEF_DATA_DIR=/data \ + PYTHONUNBUFFERED=1 + +VOLUME /data +EXPOSE 8000 + +USER soosef + +# Federation relay: only the verisoo API with federation endpoints +CMD ["uvicorn", "soosef.verisoo.api:app", "--host", "0.0.0.0", "--port", "8000"] + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" + +# === Stage 3: Full org server === +FROM python:3.12-slim-bookworm AS server + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libjpeg62-turbo libopenblas0 \ + && rm -rf /var/lib/apt/lists/* \ + && useradd -m -s /bin/bash soosef + +COPY --from=builder /opt/soosef-env /opt/soosef-env + +# Copy frontend templates and static assets +COPY frontends/ /opt/soosef-env/lib/python3.12/site-packages/frontends/ + +ENV PATH="/opt/soosef-env/bin:$PATH" \ + SOOSEF_DATA_DIR=/data \ + PYTHONUNBUFFERED=1 + +VOLUME /data +EXPOSE 5000 8000 + +USER soosef + +# Init on first run, then start web UI + federation API +CMD ["sh", "-c", "soosef init 2>/dev/null; soosef serve --host 0.0.0.0 --no-https"] + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')" diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml new file mode 100644 index 0000000..e847f3c --- /dev/null +++ b/deploy/docker/docker-compose.yml @@ -0,0 +1,51 @@ +# SooSeF Docker Compose — Three-Tier Deployment +# +# Tier 2 (Org Server): Full web UI + attestation + federation +# Tier 3 (Federation Relay): Lightweight attestation API only +# +# Usage: +# Full org server: docker compose up server +# Federation relay only: docker compose up relay +# Both (e.g., testing): docker compose up + +services: + # === Tier 2: Organizational Server === + # Full SooSeF instance with web UI, stego, attestation, federation. + # Deploy on a mini PC in the newsroom or a trusted VPS. + server: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile + target: server + ports: + - "5000:5000" # Web UI (Flask/Waitress) + - "8000:8000" # Federation API (FastAPI/uvicorn) + volumes: + - server-data:/data + environment: + - SOOSEF_DATA_DIR=/data + - VERISOO_GOSSIP_INTERVAL=60 + restart: unless-stopped + + # === Tier 3: Federation Relay === + # Lightweight relay for cross-organization attestation sync. + # Deploy on a VPS in a friendly jurisdiction (Iceland, Switzerland). + # Stores only attestation records — no key material, no stego, no web UI. + relay: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile + target: relay + ports: + - "8001:8000" # Federation API + volumes: + - relay-data:/data + environment: + - SOOSEF_DATA_DIR=/data + restart: unless-stopped + +volumes: + server-data: + driver: local + relay-data: + driver: local diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md new file mode 100644 index 0000000..ffb8812 --- /dev/null +++ b/deploy/kubernetes/README.md @@ -0,0 +1,52 @@ +# SooSeF Kubernetes Deployment + +## Architecture + +``` + Field Devices (Tier 1) + (Bootable USB + laptop) + | + | LAN / sneakernet + v + ┌───────────────────────┐ + │ Org Server (Tier 2) │ <-- server-deployment.yaml + │ Full web UI + stego │ + │ + attestation + fed │ + │ Newsroom mini PC │ + └───────────┬───────────┘ + | + | gossip / federation API + v + ┌───────────────────────┐ + │ Fed Relay (Tier 3) │ <-- relay-deployment.yaml + │ Attestation API only │ + │ VPS (Iceland, CH) │ + │ Zero key knowledge │ + └───────────────────────┘ +``` + +## Quick Start + +```bash +# Build images +docker build -t soosef-server --target server -f deploy/docker/Dockerfile . +docker build -t soosef-relay --target relay -f deploy/docker/Dockerfile . + +# Deploy to Kubernetes +kubectl apply -f deploy/kubernetes/namespace.yaml +kubectl apply -f deploy/kubernetes/server-deployment.yaml +kubectl apply -f deploy/kubernetes/relay-deployment.yaml +``` + +## Notes + +- **Single writer**: Both deployments use `replicas: 1` with `Recreate` strategy. + SooSeF uses SQLite and append-only binary logs that require single-writer access. + Do not scale horizontally. +- **PVCs**: Both deployments require persistent volumes. The server needs 10Gi, + the relay needs 5Gi. Adjust based on expected attestation volume. +- **Security**: The relay stores only attestation records (image hashes + signatures). + It never sees encryption keys, plaintext messages, or original images. + If the relay is seized, the attacker gets cryptographic hashes — nothing actionable. +- **Ingress**: Not included. Configure your own ingress controller with TLS termination. + The federation API should be TLS-encrypted in transit. diff --git a/deploy/kubernetes/namespace.yaml b/deploy/kubernetes/namespace.yaml new file mode 100644 index 0000000..2a4e05f --- /dev/null +++ b/deploy/kubernetes/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: soosef + labels: + app.kubernetes.io/name: soosef diff --git a/deploy/kubernetes/relay-deployment.yaml b/deploy/kubernetes/relay-deployment.yaml new file mode 100644 index 0000000..f51d974 --- /dev/null +++ b/deploy/kubernetes/relay-deployment.yaml @@ -0,0 +1,85 @@ +# SooSeF Federation Relay — Lightweight attestation sync relay. +# Deploy on a VPS in a favorable jurisdiction for geographic redundancy. +# Stores only attestation records — zero knowledge of encryption keys. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: soosef-relay + namespace: soosef + labels: + app.kubernetes.io/name: soosef + app.kubernetes.io/component: relay +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: soosef + app.kubernetes.io/component: relay + template: + metadata: + labels: + app.kubernetes.io/name: soosef + app.kubernetes.io/component: relay + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: relay + image: soosef-relay:latest + ports: + - containerPort: 8000 + name: federation + env: + - name: SOOSEF_DATA_DIR + value: /data + volumeMounts: + - name: data + mountPath: /data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1000m" + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 30 + volumes: + - name: data + persistentVolumeClaim: + claimName: relay-data +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: relay-data + namespace: soosef +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: soosef-relay + namespace: soosef +spec: + selector: + app.kubernetes.io/name: soosef + app.kubernetes.io/component: relay + ports: + - name: federation + port: 8000 + targetPort: 8000 + type: ClusterIP diff --git a/deploy/kubernetes/server-deployment.yaml b/deploy/kubernetes/server-deployment.yaml new file mode 100644 index 0000000..69d16b3 --- /dev/null +++ b/deploy/kubernetes/server-deployment.yaml @@ -0,0 +1,97 @@ +# SooSeF Org Server — Full deployment with persistent storage. +# For newsroom or trusted infrastructure deployment. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: soosef-server + namespace: soosef + labels: + app.kubernetes.io/name: soosef + app.kubernetes.io/component: server +spec: + replicas: 1 # Single writer — do not scale horizontally + strategy: + type: Recreate # Not RollingUpdate — SQLite + append-only logs need single writer + selector: + matchLabels: + app.kubernetes.io/name: soosef + app.kubernetes.io/component: server + template: + metadata: + labels: + app.kubernetes.io/name: soosef + app.kubernetes.io/component: server + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: soosef + image: soosef-server:latest + ports: + - containerPort: 5000 + name: web + - containerPort: 8000 + name: federation + env: + - name: SOOSEF_DATA_DIR + value: /data + - name: VERISOO_GOSSIP_INTERVAL + value: "60" + volumeMounts: + - name: data + mountPath: /data + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "2000m" + livenessProbe: + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 15 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 10 + periodSeconds: 10 + volumes: + - name: data + persistentVolumeClaim: + claimName: soosef-data +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: soosef-data + namespace: soosef +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: soosef-server + namespace: soosef +spec: + selector: + app.kubernetes.io/name: soosef + app.kubernetes.io/component: server + ports: + - name: web + port: 5000 + targetPort: 5000 + - name: federation + port: 8000 + targetPort: 8000 + type: ClusterIP diff --git a/deploy/live-usb/build.sh b/deploy/live-usb/build.sh new file mode 100755 index 0000000..548bcbf --- /dev/null +++ b/deploy/live-usb/build.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Build a bootable Debian Live USB image with SooSeF pre-installed. +# +# Prerequisites: +# apt install live-build +# +# Usage: +# cd deploy/live-usb +# sudo ./build.sh +# +# Output: live-image-amd64.hybrid.iso (flash to USB with dd or Balena Etcher) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SOOSEF_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +echo "=== SooSeF Live USB Image Builder ===" +echo "Source: $SOOSEF_ROOT" +echo + +cd "$SCRIPT_DIR" + +# Clean previous builds +lb clean 2>/dev/null || true + +# Configure live-build +lb config \ + --distribution bookworm \ + --architectures amd64 \ + --binary-images iso-hybrid \ + --memtest none \ + --bootappend-live "boot=live components locales=en_US.UTF-8 keyboard-layouts=us" \ + --apt-indices false \ + --security true \ + --updates true + +# Build +echo "Building image (this takes 10-20 minutes)..." +lb build + +echo +echo "=== Build complete ===" +echo "Image: $(ls -lh live-image-*.iso 2>/dev/null || echo 'Check for .iso file')" +echo +echo "Flash to USB:" +echo " sudo dd if=live-image-amd64.hybrid.iso of=/dev/sdX bs=4M status=progress" +echo " (replace /dev/sdX with your USB device)" diff --git a/deploy/live-usb/config/hooks/live/0100-install-soosef.hook.chroot b/deploy/live-usb/config/hooks/live/0100-install-soosef.hook.chroot new file mode 100755 index 0000000..c0cdf20 --- /dev/null +++ b/deploy/live-usb/config/hooks/live/0100-install-soosef.hook.chroot @@ -0,0 +1,26 @@ +#!/bin/bash +# Install SooSeF and all dependencies into the live image. +# This runs inside the chroot during image build. +set -euo pipefail + +echo "=== Installing SooSeF ===" + +# Create soosef user +useradd -m -s /bin/bash -G sudo soosef +echo "soosef:soosef" | chpasswd + +# Create virtual environment +python3 -m venv /opt/soosef-env +source /opt/soosef-env/bin/activate + +# Install soosef with all extras (pre-built wheels from PyPI) +pip install --no-cache-dir "soosef[web,cli,attest,stego-dct,stego-audio,fieldkit]" + +# Verify installation +python3 -c "import soosef; print(f'SooSeF {soosef.__version__} installed')" +python3 -c "from soosef.stegasoo import encode; print('stegasoo OK')" +python3 -c "from soosef.verisoo import Attestation; print('verisoo OK')" + +deactivate + +echo "=== SooSeF installation complete ===" diff --git a/deploy/live-usb/config/hooks/live/0200-harden.hook.chroot b/deploy/live-usb/config/hooks/live/0200-harden.hook.chroot new file mode 100755 index 0000000..6e5e224 --- /dev/null +++ b/deploy/live-usb/config/hooks/live/0200-harden.hook.chroot @@ -0,0 +1,39 @@ +#!/bin/bash +# Security hardening for the live image. +set -euo pipefail + +echo "=== Applying security hardening ===" + +# Disable core dumps (Python doesn't zero memory — core dumps leak keys) +echo "* hard core 0" >> /etc/security/limits.conf +echo "fs.suid_dumpable = 0" >> /etc/sysctl.d/99-soosef.conf +echo "kernel.core_pattern=|/bin/false" >> /etc/sysctl.d/99-soosef.conf + +# Disable swap (keys persist in swap pages) +systemctl mask swap.target || true +echo "vm.swappiness = 0" >> /etc/sysctl.d/99-soosef.conf + +# Enable UFW with deny-all + allow web UI +ufw default deny incoming +ufw default allow outgoing +ufw allow 5000/tcp comment "SooSeF Web UI" +ufw allow 22/tcp comment "SSH" +ufw --force enable || true + +# Disable unnecessary services +systemctl disable bluetooth.service 2>/dev/null || true +systemctl disable avahi-daemon.service 2>/dev/null || true +systemctl disable cups.service 2>/dev/null || true + +# Enable SooSeF service +systemctl enable soosef.service + +# Auto-login to openbox (so the browser opens without login prompt) +mkdir -p /etc/lightdm/lightdm.conf.d +cat > /etc/lightdm/lightdm.conf.d/50-autologin.conf << 'EOF' +[Seat:*] +autologin-user=soosef +autologin-user-timeout=0 +EOF + +echo "=== Hardening complete ===" diff --git a/deploy/live-usb/config/includes.chroot/etc/systemd/system/soosef.service b/deploy/live-usb/config/includes.chroot/etc/systemd/system/soosef.service new file mode 100644 index 0000000..763c346 --- /dev/null +++ b/deploy/live-usb/config/includes.chroot/etc/systemd/system/soosef.service @@ -0,0 +1,28 @@ +[Unit] +Description=SooSeF Security Fieldkit +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=soosef +Group=soosef +WorkingDirectory=/home/soosef +Environment=PATH=/opt/soosef-env/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +Environment=SOOSEF_DATA_DIR=/home/soosef/.soosef +ExecStartPre=/opt/soosef-env/bin/soosef init --no-identity --no-channel +ExecStart=/opt/soosef-env/bin/soosef serve --host 0.0.0.0 --no-https +Restart=on-failure +RestartSec=5 + +# Security hardening +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=read-only +ReadWritePaths=/home/soosef/.soosef +PrivateTmp=yes +ProtectKernelTunables=yes +ProtectControlGroups=yes + +[Install] +WantedBy=multi-user.target diff --git a/deploy/live-usb/config/includes.chroot/etc/xdg/openbox/autostart b/deploy/live-usb/config/includes.chroot/etc/xdg/openbox/autostart new file mode 100644 index 0000000..a7ef1e4 --- /dev/null +++ b/deploy/live-usb/config/includes.chroot/etc/xdg/openbox/autostart @@ -0,0 +1,4 @@ +# SooSeF Live USB — auto-open web UI in Firefox +# Wait for the SooSeF server to start, then open the browser +sleep 5 +firefox-esr --kiosk http://127.0.0.1:5000 & diff --git a/deploy/live-usb/config/package-lists/soosef.list.chroot b/deploy/live-usb/config/package-lists/soosef.list.chroot new file mode 100644 index 0000000..7f62ddf --- /dev/null +++ b/deploy/live-usb/config/package-lists/soosef.list.chroot @@ -0,0 +1,41 @@ +## System essentials +python3 +python3-pip +python3-venv +python3-dev + +## Build dependencies for Python packages with C extensions +libjpeg62-turbo-dev +zlib1g-dev +libffi-dev +libssl-dev +gfortran +libopenblas-dev + +## SooSeF runtime dependencies +gpsd +gpsd-clients +cryptsetup +ufw +shred + +## Useful tools +curl +wget +git +htop +usbutils +pciutils + +## GUI (minimal — just a browser for the web UI) +xorg +openbox +firefox-esr +lightdm + +## Firmware for common laptop hardware +firmware-linux-free +firmware-misc-nonfree +firmware-iwlwifi +firmware-realtek +firmware-atheros diff --git a/docs/deployment.md b/docs/deployment.md index 8882388..cd34fa0 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,195 +1,424 @@ # SooSeF Deployment Guide -Deploying SooSeF on a Raspberry Pi for airgapped LAN use. +Three-tier deployment model for field journalism, organizational evidence management, +and cross-organization federation. 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. +and anyone setting up SooSeF for operational use. Read the tier descriptions first, then +jump to the section that matches your deployment. --- -## 1. Hardware Requirements +## Deployment Overview -**Minimum:** +``` +Tier 1: Field Device Tier 2: Org Server Tier 3: Federation Relay +(Bootable USB + laptop) (Docker on mini PC / VPS) (Docker on VPS) + +Reporter in the field Newsroom / NGO office Friendly jurisdiction +Amnesic, encrypted Persistent storage Attestation sync only +Pull USB = zero trace Web UI + federation API Zero knowledge of keys + \ | / + \_____ sneakernet ____+____ gossip API ____/ +``` -- 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 +**Tier 1 -- Field Device.** A bootable Debian Live USB stick that a reporter plugs into +any x86_64 laptop. Boots into a minimal desktop with Firefox pointed at the local web UI. +LUKS-encrypted persistent partition for keys, config, and attestations. Amnesic by default: +pull the USB and the host machine retains nothing. -**Optional:** +**Tier 2 -- Org Server.** A persistent Docker deployment on a mini PC in the newsroom or a +trusted VPS. Runs the full SooSeF web UI (port 5000) and the federation API (port 8000). +Stores attestations, manages keys, and synchronizes with federation relays. -- 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). +**Tier 3 -- Federation Relay.** A lightweight Docker container on a VPS in a jurisdiction +with strong press protections (Iceland, Switzerland, Netherlands). Relays attestation +records between organizations. Stores only attestation hashes and signatures -- never keys, +plaintext, or original media. If seized, the attacker gets cryptographic hashes and nothing +actionable. --- -## 2. OS Setup +## 1. Tier 1: Field Device (Bootable USB) -Install **Raspberry Pi OS Lite (64-bit, Bookworm or later)**. The desktop environment is -unnecessary and wastes resources. +### 1.1 What you need -After first boot: +- A USB drive, 16 GB minimum (32 GB recommended for persistent storage) +- Any x86_64 laptop to boot from (ideally a refurbished ThinkPad T440/T480 -- cheap, + common, replaceable, good Linux support -- but any laptop that boots from USB works) +- A build machine with `live-build` installed (any Debian/Ubuntu system) + +The resulting USB image is a Debian Live system with SooSeF pre-installed. No pip, no +terminal, no manual setup. The reporter boots it and gets a working SooSeF instance. + +### 1.2 Building the USB image + +On the build machine: ```bash -# Set hostname -sudo hostnamectl set-hostname soosef-node +sudo apt install live-build -# 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 +cd deploy/live-usb +sudo ./build.sh ``` -Set a strong password. If possible, use SSH key authentication and disable password login -in `/etc/ssh/sshd_config` (`PasswordAuthentication no`). +This uses `lb` (live-build) to create a Debian Bookworm hybrid ISO. The build takes +10-20 minutes depending on your machine and network speed. + +Output: `live-image-amd64.hybrid.iso` + +Flash it to USB: + +```bash +sudo dd if=live-image-amd64.hybrid.iso of=/dev/sdX bs=4M status=progress +``` + +Replace `/dev/sdX` with your USB device. Double-check the device path -- `dd` will +happily overwrite your system drive. + +Alternatively, use Balena Etcher if you prefer a GUI. + +### 1.3 Boot behavior + +When a reporter boots from the USB: + +1. GRUB loads the Debian Live system +2. A minimal desktop environment starts (no login prompt) +3. Firefox opens automatically, pointed at `https://127.0.0.1:5000` +4. The SooSeF web UI prompts for first-user setup (on first boot) or login + +No terminal interaction required for normal operation. + +### 1.4 Persistent encrypted storage + +The USB image includes a LUKS-encrypted persistent partition. On first boot, the reporter +sets a passphrase. All SooSeF state lives on this partition: + +``` +/persistent/ + .soosef/ Keys, config, attestations, chain data, auth +``` + +On subsequent boots, the system prompts for the LUKS passphrase to unlock the persistent +partition. If the passphrase is not entered (or wrong), SooSeF starts in a fresh, +ephemeral state -- useful for crossing borders with a "clean" appearance. + +### 1.5 Amnesic operation + +The live system runs from RAM. When the USB is removed: + +- The host laptop retains zero SooSeF data (no files, no swap traces, no browser cache) +- The host's own storage is never written to +- RAM contents are gone on power-off + +This is the primary security property. The laptop is disposable -- if seized, it contains +nothing. The USB stick is the only thing that matters, and it is LUKS-encrypted. + +### 1.6 Killswitch on flash storage + +On a live USB, the killswitch works by destroying the LUKS key rather than trying to +overwrite individual files. This is the only reliable secure deletion method on flash +storage, because: + +- `shred` does not work reliably on flash (wear leveling preserves old blocks) +- LUKS key destruction makes the entire encrypted partition unrecoverable +- The LUKS header is small enough to fit in a single flash block, so destruction is + near-instantaneous + +When the killswitch fires on a Tier 1 device, it: + +1. Overwrites the LUKS header (renders the partition permanently unreadable) +2. Zeroes the LUKS key slots +3. Triggers a reboot (system returns to amnesic state with no persistent data) + +### 1.7 Recommended field hardware + +Any x86_64 laptop works, but refurbished ThinkPads are ideal for field use: + +- **ThinkPad T440 / T480**: common, cheap ($100-200 refurbished), excellent Linux + support, replaceable parts, inconspicuous (every office has one) +- Avoid laptops with Secure Boot that cannot be disabled (some newer consumer models) +- Avoid laptops with soldered RAM below 4 GB + +The laptop itself holds no sensitive data. If it is seized, lost, or broken, acquire +another and boot from the same USB. --- -## 3. Security Hardening +## 2. Tier 2: Org Server (Docker) -### 3.1 Disable or encrypt swap +### 2.1 What you need -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). +- A mini PC (Intel NUC, Beelink, etc.) or trusted VPS with Docker installed +- 2 GB RAM minimum, 10 GB storage minimum +- Network connectivity (LAN for newsroom, or internet for VPS with federation) -**Option A: Disable swap entirely (recommended if you have 4 GB+ RAM)** +### 2.2 Docker deployment ```bash -sudo dphys-swapfile swapoff -sudo systemctl disable dphys-swapfile -sudo rm /var/swap +cd deploy/docker + +# Start the org server +docker compose up server -d ``` -**Option B: Encrypted swap (if you need swap on 2 GB models)** +This starts the full SooSeF server with: + +- **Port 5000**: Web UI (Flask/Waitress) -- stego, attestation, key management, admin +- **Port 8000**: Federation API (FastAPI/uvicorn) -- cross-org attestation sync + +The Docker image is a multi-stage build (`deploy/docker/Dockerfile`, target: `server`). +It installs SooSeF into a Python 3.12 virtualenv, copies frontend assets, and runs +`soosef init` on first start followed by `soosef serve`. + +Data is persisted in a Docker volume (`server-data`) mounted at `/data` inside the +container. The `SOOSEF_DATA_DIR` environment variable points SooSeF at this volume. + +### 2.3 Docker Compose reference + +The full `deploy/docker/docker-compose.yml` defines both Tier 2 and Tier 3 services: + +```yaml +services: + server: # Tier 2: Full org server + ports: + - "5000:5000" # Web UI + - "8000:8000" # Federation API + volumes: + - server-data:/data + environment: + - SOOSEF_DATA_DIR=/data + - VERISOO_GOSSIP_INTERVAL=60 + + relay: # Tier 3: Federation relay + ports: + - "8001:8000" # Federation API + volumes: + - relay-data:/data + environment: + - SOOSEF_DATA_DIR=/data +``` + +Adjust port mappings and volume drivers as needed for your environment. + +### 2.4 Kubernetes deployment + +For organizations already running Kubernetes: ```bash -sudo dphys-swapfile swapoff -sudo systemctl disable dphys-swapfile +# Build images +docker build -t soosef-server --target server -f deploy/docker/Dockerfile . +docker build -t soosef-relay --target relay -f deploy/docker/Dockerfile . -# 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 +# Deploy +kubectl apply -f deploy/kubernetes/namespace.yaml +kubectl apply -f deploy/kubernetes/server-deployment.yaml ``` -Adjust the device path to match your partition layout. +Important constraints from `deploy/kubernetes/README.md`: -### 3.2 Disable core dumps +- **Single writer only.** Both deployments use `replicas: 1` with `Recreate` strategy. + SooSeF uses SQLite and append-only binary logs that require single-writer access. Do not + scale horizontally. +- **Persistent volumes required.** The server needs 10Gi, the relay needs 5Gi. Adjust + based on expected attestation volume. +- **Ingress not included.** Configure your own ingress controller with TLS termination. -A core dump from the SooSeF process would contain key material in plaintext. +### 2.5 TLS and reverse proxy + +The Docker container runs without HTTPS internally (`--no-https` in the CMD). TLS should +be handled by: + +- A reverse proxy (nginx, Caddy, Traefik) in front of the container +- Your Kubernetes ingress controller +- A cloud load balancer (if on a VPS) + +For a simple mini-PC newsroom setup without a reverse proxy, override the CMD to remove +`--no-https` and let SooSeF generate a self-signed certificate. + +### 2.6 Backups + +The Docker volume contains all SooSeF state. Back it up: ```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 +# Stop the container, snapshot the volume, restart +docker compose stop server +docker run --rm -v server-data:/data -v /backup:/backup \ + busybox tar czf /backup/soosef-$(date +%Y%m%d).tar.gz -C /data . +docker compose start server ``` -### 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 -``` +Or use `soosef keys export` from inside the container for key-only backups. --- -## 4. Installation +## 3. Tier 3: Federation Relay (Docker) -### 4.1 System dependencies +### 3.1 Purpose + +The federation relay synchronizes attestation records between organizations. It is +deliberately limited: + +- Stores only attestation records (image hashes, signatures, chain data) +- Never sees encryption keys, plaintext messages, or original images +- No web UI, no steganography features +- If seized, the attacker gets cryptographic hashes -- nothing actionable + +### 3.2 Deployment ```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 +cd deploy/docker + +# Start the relay +docker compose up relay -d ``` -`libjpeg-dev` is required for Pillow and jpeglib (DCT steganography). `libffi-dev` is -required for argon2-cffi. +This starts the relay with: -### 4.2 Create virtual environment +- **Port 8001** (mapped to internal 8000): Federation API (FastAPI/uvicorn) +- Data volume: `relay-data` at `/data` + +The Docker image uses the `relay` target from the same multi-stage Dockerfile. The relay +runs only `uvicorn soosef.verisoo.api:app` -- the minimal federation endpoint. + +### 3.3 Kubernetes deployment ```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 +kubectl apply -f deploy/kubernetes/namespace.yaml +kubectl apply -f deploy/kubernetes/relay-deployment.yaml ``` -### 4.3 Install SooSeF +Same constraints as Tier 2: single replica, persistent volume (5Gi), bring your own +ingress with TLS. -**If the Pi has internet access (pre-deployment):** +### 3.4 Jurisdiction considerations -```bash -pip install "soosef[web,cli,fieldkit]" -``` +The relay should be hosted in a jurisdiction with: -For hardware killswitch support via GPIO: +- Strong press freedom protections +- No mandatory data retention laws that apply to this type of data +- Legal resistance to foreign seizure orders -```bash -pip install "soosef[rpi]" -``` +Recommended: **Iceland**, **Switzerland**, **Netherlands**. These are suggestions, not +guarantees -- consult with a press freedom lawyer for your specific situation. -The `rpi` extra includes `web`, `cli`, `fieldkit`, and `gpiozero`. +### 3.5 What happens if the relay is compromised -**For airgapped install (no internet on the Pi):** +The relay stores attestation records: SHA-256 hashes of images, Ed25519 signatures, chain +linkage data, and signer public keys. It does not store: -On an internet-connected machine with the same architecture (aarch64 for RPi 4): +- Encryption keys (AES-256-GCM channel keys or Ed25519 private keys) +- Original images or media +- Steganographic payloads or plaintext messages +- User credentials or session data -```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 -``` +An attacker who seizes the relay learns that certain public keys signed certain image +hashes at certain times. They cannot recover the images, decrypt messages, or forge new +attestations. --- -## 5. Initial Setup +## 4. Threat Level Configuration Presets + +SooSeF ships four configuration presets at `deploy/config-presets/`. Choose one based on +your operational threat environment and copy it to your config location. + +```bash +cp deploy/config-presets/high-threat.json ~/.soosef/config.json +``` + +### 4.1 Low Threat -- Press Freedom Country + +File: `deploy/config-presets/low-threat.json` + +For deployments in the Nordics, New Zealand, Canada, and similar environments where the +risk is accidental data loss rather than adversarial seizure. + +| Setting | Value | +|---|---| +| Session timeout | 30 minutes | +| Killswitch | Disabled | +| Dead man's switch | Disabled | +| USB monitoring | Disabled | +| Tamper monitoring | Disabled | +| Backup reminders | Every 14 days | +| Cover name | None | + +Chain and attestation are enabled for provenance integrity. Fieldkit security features +are off because the threat model does not warrant them. + +### 4.2 Medium Threat -- Restricted Press + +File: `deploy/config-presets/medium-threat.json` + +For deployments in Turkey, Hungary, India, and similar environments with legal pressure +and risk of device seizure at borders. + +| Setting | Value | +|---|---| +| Session timeout | 15 minutes | +| Killswitch | Enabled | +| Dead man's switch | 48 hours / 4 hours grace | +| USB monitoring | Enabled | +| Tamper monitoring | Disabled | +| Login lockout | 5 attempts / 15 min lockout | +| Backup reminders | Every 7 days | +| Cover name | "Office Document Manager" | + +### 4.3 High Threat -- Active Conflict Zone + +File: `deploy/config-presets/high-threat.json` + +For deployments in Syria, Myanmar, Ethiopia, Iran, and similar environments with risk of +raids, equipment seizure, and physical coercion. + +| Setting | Value | +|---|---| +| Session timeout | 5 minutes | +| Killswitch | Enabled | +| Dead man's switch | 12 hours / 1 hour grace | +| USB monitoring | Enabled | +| Tamper monitoring | Enabled | +| Login lockout | 3 attempts / 30 min lockout | +| Backup reminders | Daily | +| Cover name | "Local Inventory Tracker" | + +### 4.4 Critical Threat -- Targeted Surveillance + +File: `deploy/config-presets/critical-threat.json` + +For a specific journalist or organization targeted by a state actor (Pegasus-level threat). + +| Setting | Value | +|---|---| +| Host binding | 127.0.0.1 only (access via SSH tunnel) | +| Session timeout | 3 minutes | +| Killswitch | Enabled | +| Dead man's switch | 6 hours / 1 hour grace | +| USB monitoring | Enabled | +| Tamper monitoring | Enabled | +| Login lockout | 3 attempts / 60 min lockout | +| Backup reminders | Daily | +| Cover name | "System Statistics" | + +At this threat level, also consider: full-disk encryption on all devices, removing SSH +after initial setup, running the web UI as a Tor hidden service, and physical security +measures that are outside the scope of this software. + +### 4.5 Customizing presets + +The presets are starting points. Override individual settings in `~/.soosef/config.json` +after copying. The full configuration reference is in Section 8. + +--- + +## 5. Initial Setup (All Tiers) ### 5.1 Initialize SooSeF +On Tier 1 (USB), initialization happens automatically on first boot. On Tier 2/3 (Docker), +the container runs `soosef init` on first start. For manual installs: + ```bash soosef init ``` @@ -225,7 +454,15 @@ The `identity/` and `auth/` directories are created with mode 0700. - A channel key (for steganographic encoding) - A default `config.json` -### 5.2 First user setup +### 5.2 Apply a threat level preset + +```bash +cp deploy/config-presets/-threat.json ~/.soosef/config.json +``` + +See Section 4 for preset descriptions. + +### 5.3 First user setup Start the server and create the first admin user through the web UI: @@ -233,17 +470,86 @@ Start the server and create the first admin user through the web UI: soosef serve --host 0.0.0.0 --no-https ``` -Navigate to `http://:5000` from a device on the same LAN. The web UI will prompt -you to create the first user account. +Navigate to `http://:5000` from a device on the same network. 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). +(see Section 7). --- -## 6. Running +## 6. Security Hardening -### 6.1 Basic usage +These hardening steps apply primarily to Tier 2 (org server on bare metal) and to the +build environment for Tier 1 USB images. Docker deployments handle most of this within +the container, but the host machine still needs attention. + +### 6.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 flash +storage (SSDs, USB drives), this data may survive even after the swap file is deleted +due to wear leveling. + +**Option A: Disable swap entirely (recommended if you have 4 GB+ RAM)** + +```bash +sudo swapoff -a +# Remove or comment out swap entries in /etc/fstab +``` + +**Option B: Encrypted swap** + +```bash +# Use dm-crypt with a random key (regenerated each boot) +echo "swap /dev/sdXN /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. + +### 6.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 +``` + +### 6.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 allow 8000/tcp # Federation API (Tier 2 only) +sudo ufw enable +``` + +If running fully airgapped, also deny outgoing: + +```bash +sudo ufw default deny outgoing +``` + +### 6.4 Disable unnecessary services + +```bash +sudo systemctl disable bluetooth +sudo systemctl disable avahi-daemon +``` + +Adjust based on what your system has running. The goal is to minimize attack surface. + +--- + +## 7. Running + +### 7.1 Basic usage (bare metal / Tier 2 without Docker) ```bash # LAN-only, no HTTPS (acceptable if the network is physically isolated) @@ -264,7 +570,7 @@ 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 +### 7.2 systemd service (bare metal Tier 2) Create `/etc/systemd/system/soosef.service`: @@ -307,7 +613,7 @@ unnecessary. --- -## 7. Configuration +## 8. Configuration Reference 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. @@ -333,33 +639,35 @@ panel. All fields have sensible defaults -- you only need to set what you want t | `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. `soosef status` reports overdue backups. | -| `cover_name` | `""` | If set, used as the CN in the self-signed SSL certificate instead of "localhost". See Section 14 (Cover/Duress Mode). | -| `gpio_killswitch_pin` | `17` | GPIO pin for hardware killswitch button. | -| `gpio_killswitch_hold_seconds` | `5.0` | Required hold time to trigger hardware killswitch. | +| `cover_name` | `""` | If set, used as the CN in the self-signed SSL certificate instead of "localhost". See Section 15 (Cover/Duress Mode). | -Example minimal config for a field deployment: +Example minimal config for a high-threat field deployment: ```json { "host": "0.0.0.0", "port": 5000, "https_enabled": true, - "session_timeout_minutes": 10, + "session_timeout_minutes": 5, "deadman_enabled": true, "deadman_interval_hours": 12, "deadman_grace_hours": 1, "deadman_warning_webhook": "https://hooks.example.com/alert", "killswitch_enabled": true, - "backup_reminder_days": 3, - "cover_name": "Local Inventory Manager" + "backup_reminder_days": 1, + "cover_name": "Local Inventory Tracker" } ``` --- -## 8. Fieldkit Setup +## 9. Fieldkit Setup -### 8.1 Dead man's switch +Fieldkit features apply to all tiers, but their behavior differs slightly based on the +deployment type. On Tier 1 (USB), the killswitch destroys the LUKS key. On Tier 2/3 +(Docker or bare metal), it shreds files and uninstalls packages. + +### 9.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 @@ -407,7 +715,7 @@ Disarm: soosef fieldkit deadman disarm ``` -### 8.2 Geofence +### 9.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. @@ -416,9 +724,9 @@ killswitch if the device moves outside the fence. soosef fieldkit geofence set --lat 50.4501 --lon 30.5234 --radius 5000 ``` -Coordinates are in decimal degrees, radius in meters. +Coordinates are in decimal degrees, radius in meters. Most useful on Tier 1 field devices. -### 8.3 USB whitelist +### 9.3 USB whitelist Record currently connected USB devices as the trusted baseline: @@ -429,7 +737,7 @@ soosef fieldkit usb snapshot When monitoring is enabled, SooSeF will alert (or trigger killswitch, depending on config) if an unknown USB device is connected. -### 8.4 Tamper baseline +### 9.4 Tamper baseline Record file integrity baselines for critical files: @@ -439,10 +747,10 @@ soosef fieldkit tamper baseline SooSeF monitors for unexpected changes to tracked files when tamper monitoring is enabled. -### 8.5 Killswitch +### 9.5 Killswitch -The killswitch destroys all key material and data in a deliberate order designed to -maximize what is gone before any interruption. The destruction sequence is: +The killswitch destroys all key material and data. The destruction sequence is designed to +maximize what is gone before any interruption: 1. **Ed25519 identity keys** (most critical -- without these, signed attestations cannot be forged and encrypted data is unrecoverable) @@ -456,14 +764,18 @@ maximize what is gone before any interruption. The destruction sequence is: 9. **Deep forensic scrub** (see below) 10. **Self-uninstall** of the soosef pip package +On Tier 1 (USB), steps 1-10 are replaced by LUKS header destruction, which is faster and +more reliable on flash media (see Section 1.6). + Trigger manually: ```bash soosef fieldkit purge --confirm CONFIRM-PURGE ``` -**Deep forensic scrub.** When the killswitch fires with `ALL` scope, it performs a deep -forensic scrub that removes traces of SooSeF beyond the `~/.soosef/` directory: +**Deep forensic scrub (Tier 2 bare metal only).** When the killswitch fires with `ALL` +scope on a non-USB deployment, it performs a deep forensic scrub that removes traces of +SooSeF beyond the `~/.soosef/` directory: - **Python bytecache**: removes all `__pycache__` directories and `.pyc` files for soosef, stegasoo, and verisoo from site-packages @@ -478,12 +790,13 @@ forensic scrub that removes traces of SooSeF beyond the `~/.soosef/` directory: After a full purge, the system will show minimal evidence that SooSeF was ever installed. Note that this is best-effort -- filesystem journal entries, inode metadata, and flash -wear-leveling remnants may still exist. For complete deniability, use full-disk encryption -(LUKS) and physically destroy the storage media. +wear-leveling remnants may still exist. For complete deniability on Tier 2, use full-disk +encryption (LUKS) and physically destroy the storage media. On Tier 1, LUKS header +destruction handles this. --- -## 9. Key Management +## 10. Key Management SooSeF manages two separate key domains: @@ -495,7 +808,7 @@ SooSeF manages two separate key domains: These are separate security concerns and are never merged. -### 9.1 Backup +### 10.1 Backup Back up keys regularly. SooSeF warns if no backup has been taken within the `backup_reminder_days` window (default: 7 days). @@ -505,15 +818,18 @@ soosef keys export /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. +drive physically separate from the device. -### 9.2 Restore +On Tier 1 (USB), key backups are critical -- if the USB stick is lost or destroyed, the +keys are gone. Back up to a second USB drive and store it in a separate location. + +### 10.2 Restore ```bash soosef keys import /media/usb/soosef-backup.enc ``` -### 9.3 Key rotation +### 10.3 Key rotation Rotate the identity keypair (old key is archived, not destroyed): @@ -530,7 +846,7 @@ soosef keys rotate-channel After rotating keys, take a fresh backup immediately. Notify all collaborators of the new identity fingerprint so they can update their trusted-key lists. -### 9.4 Trusting collaborator keys +### 10.4 Trusting collaborator keys Import a collaborator's public key so you can verify their attestations: @@ -542,13 +858,13 @@ Verify the fingerprint out-of-band (in person, over a secure channel) before tru --- -## 10. Source Drop Box +## 11. Source Drop Box The source drop box provides a SecureDrop-like anonymous file intake that runs inside SooSeF. Sources do not need a SooSeF account -- they receive a one-time upload URL and submit files through their browser. -### 10.1 Creating tokens +### 11.1 Creating tokens An admin creates a time-limited upload token through the web UI at `/dropbox/admin` or through the admin panel. Each token has: @@ -560,7 +876,7 @@ through the admin panel. Each token has: Tokens are 32-byte cryptographically random URL-safe strings. Once created, the admin receives a URL of the form `https://:/dropbox/upload/`. -### 10.2 Sharing URLs securely +### 11.2 Sharing URLs securely Share the upload URL with the source over an already-secure channel: @@ -570,7 +886,7 @@ Share the upload URL with the source over an already-secure channel: Never send drop box URLs over unencrypted email or SMS. -### 10.3 What happens on upload +### 11.3 What happens on upload When a source uploads files: @@ -584,13 +900,13 @@ When a source uploads files: each file 5. The token usage counter increments; once max files is reached, the link stops accepting -### 10.4 Receipt verification +### 11.4 Receipt verification Sources can verify their submission was received by posting their receipt code to `/dropbox/verify-receipt`. This returns the filename, SHA-256, and reception timestamp if the receipt is valid. -### 10.5 Operational security for the drop box +### 11.5 Operational security for the drop box - **No SooSeF branding**: the upload page is a minimal HTML form with no identifying marks, styled generically @@ -612,7 +928,7 @@ If operating in a high-risk environment, consider running SooSeF as a Tor hidden (`.onion` address). Configure a torrc hidden service pointing to `127.0.0.1:5000` and share the `.onion` URL instead of a LAN address. -### 10.6 Drop box file storage +### 11.6 Drop box file storage Uploaded files are stored in `~/.soosef/temp/dropbox/` with filenames derived from the SHA-256 prefix. This directory has mode 0700. Token metadata and receipts are stored in a @@ -620,13 +936,13 @@ SQLite database at `~/.soosef/auth/dropbox.db`. --- -## 11. Chain Anchoring +## 12. Chain Anchoring Chain anchoring externally proves that your attestation chain existed before a given time. A single anchor for the chain head implicitly timestamps every record that preceded it, because the chain is append-only with hash linkage. -### 11.1 When to anchor +### 12.1 When to anchor Anchor your chain: @@ -635,7 +951,7 @@ Anchor your chain: - Before and after major investigations or events - Before key rotation (locks the existing chain state) -### 11.2 Automated anchoring (RFC 3161 TSA) +### 12.2 Automated anchoring (RFC 3161 TSA) If the device has internet access (even temporarily), submit the chain head to a Timestamping Authority: @@ -650,7 +966,7 @@ both the anchor and the TSA response as a JSON file under `~/.soosef/chain/ancho The TSA token is a cryptographically signed proof from a third party that the hash existed at the timestamp. This is legally stronger than a self-asserted timestamp. -### 11.3 Manual anchoring +### 12.3 Manual anchoring Without `--tsa`, the command exports the anchor hash for manual external submission: @@ -669,9 +985,9 @@ digest hash. Publish this text to any external witness: The anchor file is saved locally regardless of whether a TSA was used. -### 11.4 Airgapped anchoring procedure +### 12.4 Airgapped anchoring procedure -For fully airgapped deployments: +For Tier 1 (USB) and other airgapped deployments: 1. Run `soosef chain anchor` on the airgapped device 2. Copy the printed anchor text to a USB drive (text file, photograph of screen, or @@ -681,7 +997,7 @@ For fully airgapped deployments: 4. Document the publication (URL, screenshot, transaction ID) and store it alongside the USB key backup -### 11.5 Verifying anchors +### 12.5 Verifying anchors To verify that the current chain state matches a previously created anchor: @@ -694,13 +1010,19 @@ with since the anchor was created, verification will fail. --- -## 12. Cross-Organization Federation +## 13. Cross-Organization Federation Federation allows multiple SooSeF instances to exchange attestation records for collaborative investigations. Bundles are self-authenticating: each record carries the signer's public key, so the importer can verify signatures against their trust store. -### 12.1 Exchanging trust keys +In the three-tier model, federation traffic flows: + +- **Tier 1 to Tier 2**: via sneakernet (USB drive) or LAN sync +- **Tier 2 to Tier 3**: via the federation API (port 8000) over the internet +- **Tier 2 to Tier 2**: via Tier 3 relay, or directly via sneakernet + +### 13.1 Exchanging trust keys Before two organizations can exchange attestation bundles, they must trust each other's identity keys. @@ -722,7 +1044,7 @@ soosef keys trust --import /media/usb/org-a-pubkey.pem Always verify fingerprints out-of-band (in person, over a known-secure voice channel). Repeat in both directions so each organization trusts the other. -### 12.2 Exporting attestation bundles +### 13.2 Exporting attestation bundles Export a JSON bundle containing attestation records and chain data: @@ -744,7 +1066,7 @@ The export includes: - A standalone verification script (requires only Python + `cryptography`) - A human-readable README -### 12.3 Importing attestation bundles +### 13.3 Importing attestation bundles On the receiving organization's SooSeF instance: @@ -752,14 +1074,14 @@ On the receiving organization's SooSeF instance: - Records signed by untrusted fingerprints are rejected (unless trust-on-first-use is used) - Duplicate records (matching SHA-256) are skipped automatically -### 12.4 Delivery acknowledgments +### 13.4 Delivery acknowledgments When a bundle is imported and the receiving instance has a chain store and private key, SooSeF automatically creates a delivery acknowledgment record in the local chain. This records the bundle hash, sender fingerprint, and count of records received. The acknowledgment provides a cryptographic receipt that the bundle was delivered and ingested. -### 12.5 Selective disclosure for legal proceedings +### 13.5 Selective disclosure for legal proceedings To produce evidence for a court order or legal discovery request without revealing the entire chain: @@ -772,7 +1094,7 @@ This exports a proof bundle where the selected records are shown in full and all records appear only as hashes. A third party can verify that the selected records are part of an unbroken hash chain without seeing the contents of other records. -### 12.6 Federation on airgapped networks +### 13.6 Federation on airgapped networks All federation is designed for offline/sneakernet operation: @@ -781,15 +1103,16 @@ All federation is designed for offline/sneakernet operation: 3. Import the bundle 4. Export the delivery acknowledgment back on USB if needed -No network connectivity is required at any point. +No network connectivity is required at any point. This is the expected workflow for +Tier 1 field devices. --- -## 13. Evidence Packages and Cold Archives +## 14. Evidence Packages and Cold Archives SooSeF provides two export formats for preserving evidence outside the running instance. -### 13.1 Evidence packages +### 14.1 Evidence packages An evidence package is a self-contained ZIP that bundles everything needed for independent verification of specific attested images. Use evidence packages when you need to hand off @@ -816,7 +1139,7 @@ and verifies chain hash linkage. - Before any action that might destroy the running instance (travel through hostile checkpoints, anticipated raids) -### 13.2 Cold archives +### 14.2 Cold archives A cold archive is a full snapshot of the entire SooSeF evidence store, designed for long-term preservation. It follows OAIS (ISO 14721) alignment: the archive is @@ -851,11 +1174,11 @@ soosef archive import - Before anticipated risk events - When archiving a completed investigation -### 13.3 Legal discovery workflow +### 14.3 Legal discovery workflow For legal discovery and court proceedings: -1. Use `soosef chain disclose` for selective disclosure (Section 12.5) when you must +1. Use `soosef chain disclose` for selective disclosure (Section 13.5) when you must respond to a specific request without revealing the full chain 2. Use evidence packages for handing specific images and their attestations to counsel 3. Use cold archives when full preservation is required @@ -863,7 +1186,7 @@ For legal discovery and court proceedings: All three formats include standalone verification scripts so that the receiving party does not need to install SooSeF. -### 13.4 Long-term archival best practices +### 14.4 Long-term archival best practices - Store cold archives on at least two separate physical media (USB drives, optical discs) - Keep one copy offsite (safe deposit box, trusted third party) @@ -877,12 +1200,12 @@ not need to install SooSeF. --- -## 14. Cover/Duress Mode +## 15. Cover/Duress Mode Cover mode disguises a SooSeF installation so that casual inspection of the device does not immediately reveal it as a security toolkit. -### 14.1 Renaming the data directory +### 15.1 Renaming the data directory By default, SooSeF stores everything under `~/.soosef/`. To use an inconspicuous name, set the `SOOSEF_DATA_DIR` environment variable: @@ -899,13 +1222,10 @@ the systemd service file: Environment="SOOSEF_DATA_DIR=/home/soosef/.local/share/inventory" ``` -You can also pass `--data-dir` to any command: +In Docker deployments, set this in the environment section of the compose file. The +default Docker configuration already uses `/data`. -```bash -soosef --data-dir ~/.local/share/inventory serve --host 0.0.0.0 -``` - -### 14.2 Cover name for SSL certificates +### 15.2 Cover name for SSL certificates Set `cover_name` in config to change the Common Name (CN) in the self-signed SSL certificate. Without this, the certificate CN defaults to "localhost". With a cover name, @@ -920,23 +1240,20 @@ a browser inspector sees a plausible-looking certificate: Delete `~/.soosef/certs/cert.pem` and restart the server to regenerate the certificate with the new CN. -### 14.3 Portable USB operation +The threat level presets (Section 4) include appropriate cover names for each level. -SooSeF can run entirely from a USB drive: +### 15.3 Portable USB operation (Tier 1) -1. Install SooSeF into a virtualenv on the USB drive -2. Set `SOOSEF_DATA_DIR` to a directory on the USB -3. Run `soosef serve` from the USB +The Tier 1 bootable USB is the primary cover mechanism. When the USB is not inserted, the +host laptop shows no trace of SooSeF. The USB itself is a LUKS-encrypted partition that +reveals nothing without the passphrase. -When you remove the USB, no trace of SooSeF remains on the host machine (assuming no -swap, no core dumps, and the host filesystem was not used for temp files). - -Combine with the `cover_name` setting and a renamed data directory for maximum -deniability. +For additional cover, the USB can be labeled generically (e.g., "DEBIAN LIVE") and the +LUKS partition does not advertise its contents. --- -## 15. Operational Security Notes +## 16. Operational Security Notes SooSeF is a tool, not a shield. Understand what it cannot do. @@ -952,11 +1269,16 @@ SooSeF is a tool, not a shield. Understand what it cannot do. 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. +- **Tier 3 relay compromise.** If the relay is seized, attestation metadata (hashes, + signatures, timestamps) is exposed. This reveals that certain public keys attested + certain content at certain times. It does not reveal the content itself, but it may + reveal patterns of activity. ### 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: +The killswitch uses `shred` on Linux (3-pass overwrite + zero) for Tier 2 bare metal +deployments. On spinning disks, this is effective. On SSDs and flash storage, **it is not +reliable** because: - Flash translation layers remap physical blocks. Overwritten data may persist on remapped blocks. @@ -968,40 +1290,45 @@ the data. Even if fragments of encrypted data survive on flash, they are useless 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. +On Tier 1 (USB), the killswitch destroys the LUKS header instead of shredding individual +files, which is the only reliable approach for flash media. ### 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: +Tier 1 (USB) uses LUKS by default for the persistent partition. For Tier 2 bare metal +deployments, encrypt the data partition with LUKS. This way, even if the storage 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). +For Docker deployments, use encrypted volumes or an encrypted filesystem on the host. ### 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 +in process memory and swap. The mitigations in Section 6 (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. --- -## 16. Troubleshooting +## 17. Troubleshooting ### Health check -SooSeF exposes a `/health` endpoint on the web UI. Hit it from the LAN to verify the -server is running: +SooSeF exposes a `/health` endpoint on the web UI. Hit it to verify the server is running: ```bash -curl -k https://:5000/health +# Tier 1 or bare metal +curl -k https://127.0.0.1:5000/health + +# Tier 2 Docker +curl http://localhost:5000/health + +# Tier 3 relay +curl http://localhost:8001/health ``` The `-k` flag skips certificate verification for self-signed certs. @@ -1017,46 +1344,25 @@ chain status, and backup status. Use `--json` for machine-readable output. ### 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` +3. Check the service is running: `sudo systemctl status soosef` (bare metal) or + `docker compose ps` (Docker) +4. Check the machine's IP: `ip addr show` + +**Docker container exits immediately** + +Check the logs: + +```bash +docker compose logs server +``` + +Common causes: port conflict (5000 or 8000 already in use), volume permission issues, +or missing initialization. The container runs `soosef init` on first start, which requires +write access to the `/data` volume. **Certificate warnings in browser** @@ -1066,10 +1372,10 @@ trusted certificate. **Dead man's switch fires unexpectedly** -The switch checks every 60 seconds. If the Pi loses power or the service crashes and +The switch checks every 60 seconds. If the server 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. +start. Make sure the systemd service is set to `Restart=on-failure` (bare metal) or +`restart: unless-stopped` (Docker) and the system has reliable power. If you need to perform maintenance, disarm the switch first: @@ -1083,15 +1389,12 @@ Re-arm when maintenance is complete. 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/ -``` +Always run SooSeF as the same user. In Docker, the container runs as the `soosef` user +created during image build. **Drop box tokens expire immediately** -Token expiry is checked against UTC. If the Pi's system clock is wrong, tokens may appear +Token expiry is checked against UTC. If the system clock is wrong, tokens may appear expired as soon as they are created. Verify the clock: ```bash @@ -1106,9 +1409,10 @@ sudo date -s "2026-04-01 12:00:00" **Chain anchor TSA submission fails** -TSA submission requires network access. On an airgapped device, use manual anchoring -instead (`soosef chain anchor` without `--tsa`). If the TSA URL is unreachable, the anchor -is still saved locally -- only the external timestamp token is missing. +TSA submission requires network access. On Tier 1 (USB) or other airgapped devices, use +manual anchoring instead (`soosef chain anchor` without `--tsa`). If the TSA URL is +unreachable, the anchor is still saved locally -- only the external timestamp token is +missing. **SSL certificate shows wrong name** @@ -1137,3 +1441,45 @@ source verify-env/bin/activate pip install cryptography python verify.py ``` + +**Tier 1 USB fails to boot on a laptop** + +- Ensure Secure Boot is disabled in BIOS/UEFI settings +- Set the USB drive as the first boot device (or use the one-time boot menu, usually F12) +- If the laptop has only UEFI boot (no legacy/CSM), the live image should still work -- + it is built as a hybrid ISO -- but some very old or very locked-down firmware may not + cooperate + +**Kubernetes pod stuck in CrashLoopBackOff** + +Check logs with `kubectl logs -n soosef `. Common cause: the PersistentVolumeClaim +is not bound. Verify with `kubectl get pvc -n soosef`. The server needs 10Gi and the relay +needs 5Gi. + +--- + +## Appendix A: Legacy Raspberry Pi Deployment + +The Raspberry Pi was the original deployment target for SooSeF. It remains a viable +option for fixed installations (e.g., a permanently installed newsroom server that does +not need to be portable). The three-tier model supersedes the RPi as the primary +deployment for most use cases because: + +- Tier 1 (USB) is more portable and amnesic than a Pi with an SD card +- Tier 2 (Docker) is more maintainable than a bare-metal Pi install +- The Pi's ARM architecture complicates dependency builds (scipy, Pillow) + +If you still want to use a Raspberry Pi for a fixed Tier 2 server: + +- Use Raspberry Pi 4 Model B, 4 GB RAM minimum +- Use Raspberry Pi OS Lite (64-bit, Bookworm) +- Install system dependencies: `python3 python3-pip python3-venv python3-dev libjpeg-dev + libjpeg62-turbo-dev zlib1g-dev libffi-dev libssl-dev gfortran libopenblas-dev` +- Follow the Tier 2 bare metal instructions (systemd service, hardening) in this guide +- For GPIO hardware killswitch support, install the `rpi` extra: + `pip install "soosef[rpi]"` (includes `gpiozero`) +- Wire a momentary push button between GPIO 17 and 3.3V with a 10k pull-down to GND. + Default: 5-second hold to trigger. Configurable via `gpio_killswitch_hold_seconds` + +Note: scipy may fail to build on low-memory Pis. Use `pip install --prefer-binary scipy` +or temporarily enable swap during the build.