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:

AreaAction
SSHDisable password auth, use Ed25519 keys, set PermitRootLogin no, consider a non-default port
FirewallAllow only SSH + 443 (HTTPS). On Linux: ufw default deny incoming && ufw allow 22/tcp && ufw allow 443/tcp && ufw enable
Fail2banapt install fail2ban — protects SSH and can monitor Docker logs for repeated 401/429 responses
UpdatesEnable automatic security updates (unattended-upgrades on Debian/Ubuntu)
UsersLock 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: true network (ollama-internal) — no direct external access
  • Binds services to 127.0.0.1 where 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-init container, 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:

NetworkTypePurpose
pisovereign-networkbridgeMain service communication
ollama-internalinternal bridgeIsolates 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

  1. Isolate — stop external access: docker compose down or firewall deny-all
  2. Preserve evidence — copy container logs: docker compose logs > incident-$(date +%Y%m%d).log
  3. Rotate credentials:
    docker compose exec vault vault kv put secret/pisovereign/api-keys \
      admin="$(openssl rand -base64 32)"
    
  4. Review access — check Docker logs, Vault audit log, SSH lastlog
  5. 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

References