Security Hardening
Production security guide for PiSovereign deployments
Security Architecture
┌─────────────────────────────────────────────────┐
│ Network: Traefik TLS 1.3 + Docker isolation │
├─────────────────────────────────────────────────┤
│ Application: Rate limiting, auth, validation │
├─────────────────────────────────────────────────┤
│ Secrets: HashiCorp Vault, encrypted storage │
├─────────────────────────────────────────────────┤
│ Host: SSH hardened, firewall, auto-updates │
└─────────────────────────────────────────────────┘
Principles: Defense in depth — least privilege — fail secure — audit everything.
Host Security Basics
Docker provides process isolation, but the host still needs hardening. Apply these essentials on any machine running PiSovereign:
| Area | Action |
|---|---|
| SSH | Disable password auth, use Ed25519 keys, set PermitRootLogin no, consider a non-default port |
| Firewall | Allow only SSH + 443 (HTTPS). On Linux: ufw default deny incoming && ufw allow 22/tcp && ufw allow 443/tcp && ufw enable |
| Fail2ban | apt install fail2ban — protects SSH and can monitor Docker logs for repeated 401/429 responses |
| Updates | Enable automatic security updates (unattended-upgrades on Debian/Ubuntu) |
| Users | Lock root (passwd -l root), use a personal account with sudo |
For comprehensive OS hardening, refer to the CIS Benchmark for your distribution.
Application Security
Rate Limiting
[security]
rate_limit_enabled = true
rate_limit_rpm = 120 # Per IP per minute
[api]
max_request_size_bytes = 1048576 # 1 MB
request_timeout_secs = 30
API Authentication
Generate and store API keys in Vault:
docker compose exec vault vault kv put secret/pisovereign/api-keys \
admin="$(openssl rand -base64 32)"
All requests require Authorization: Bearer <api-key>. Invalid keys return a generic 401 — no information leakage. Rate limiting is applied per key.
Input Validation
PiSovereign validates all inputs automatically:
- Maximum lengths enforced on all string fields
- Content-type verification
- JSON schema validation
- Path traversal protection
- SQL injection prevention via parameterized queries
Container Isolation
Docker Compose provides process-level isolation. The default stack additionally:
- Runs Ollama on an
internal: truenetwork (ollama-internal) — no direct external access - Binds services to
127.0.0.1where possible (Baïkal, Vault UI) - Uses read-only filesystem mounts for config files
- Limits container capabilities via Docker defaults
Vault Security
PiSovereign uses a ChainedSecretStore — Vault is the primary store with config.toml as fallback. See Vault Setup for initial configuration.
Seal/Unseal
The Docker stack auto-initializes and auto-unseals Vault for convenience. In production, consider:
- Manual unseal: Remove the
vault-initcontainer, unseal interactively after each restart - Key splitting (Shamir’s Secret Sharing):
vault operator init -key-shares=5 -key-threshold=3— distribute shares to different people/locations - Cloud KMS auto-unseal: Use AWS KMS, GCP KMS, or Azure Key Vault for unattended unseal without storing keys locally
Token Management
PiSovereign uses AppRole authentication with short-lived tokens:
# Tokens expire after 1 hour, max 4 hours
docker compose exec vault vault write auth/approle/role/pisovereign \
token_policies="pisovereign" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=24h
Best practices:
- Use short TTLs (1 hour default is good)
- Rotate secret IDs regularly
- Never log tokens
- Revoke tokens on application shutdown
Audit Logging
docker compose exec vault vault audit enable file \
file_path=/vault/logs/audit.log
Network Security
TLS Configuration
Traefik handles TLS termination. Harden the defaults:
# docker/traefik/dynamic.yml
tls:
options:
default:
minVersion: VersionTLS13
cipherSuites:
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
curvePreferences:
- X25519
- CurveP384
sniStrict: true
In config.toml:
[security]
min_tls_version = "1.3"
tls_verify_certs = true
Network Isolation
The Docker Compose stack defines two networks:
| Network | Type | Purpose |
|---|---|---|
pisovereign-network | bridge | Main service communication |
ollama-internal | internal bridge | Isolates Ollama — no external access |
Traefik is the only service exposed to the host network. All other services communicate internally.
Security Monitoring
Configure structured JSON logging:
[logging]
level = "info"
format = "json"
include_request_id = true
include_user_id = true
Key events to monitor:
- Failed authentication attempts (401s)
- Rate limit triggers (429s)
- Vault access failures
- Unusual request patterns
See Monitoring for Prometheus alert rules covering HighFailedAuthRate and RateLimitTriggered.
Incident Response
- Isolate — stop external access:
docker compose downor firewall deny-all - Preserve evidence — copy container logs:
docker compose logs > incident-$(date +%Y%m%d).log - Rotate credentials:
docker compose exec vault vault kv put secret/pisovereign/api-keys \ admin="$(openssl rand -base64 32)" - Review access — check Docker logs, Vault audit log, SSH
lastlog - Restore from known-good backup if needed
Security Checklist
Initial Setup
- Host SSH uses key-only authentication
- Firewall allows only required ports
- Automatic security updates enabled
- Default passwords changed
Application
- Rate limiting enabled
- API keys stored in Vault
- TLS 1.3 minimum enforced
- Logs do not contain secrets
Vault
- Unseal keys secured (not on same host in production)
- AppRole configured with short TTLs
- Audit logging enabled
Ongoing
- Monthly credential rotation
- Review Vault audit logs
- Keep Docker images updated
- Review container security scans