Files
flaskpaste/documentation/deployment.md
Username 89eee3378a security: implement pentest remediation (PROXY-001, BURN-001, RATE-001)
PROXY-001: Add startup warning when TRUSTED_PROXY_SECRET empty in production
- validate_security_config() checks for missing proxy secret
- Additional warning when PKI enabled without proxy secret
- Tests for security configuration validation

BURN-001: HEAD requests now trigger burn-after-read deletion
- Prevents attacker from probing paste existence before retrieval
- Updated test to verify new behavior

RATE-001: Add RATE_LIMIT_MAX_ENTRIES to cap memory usage
- Default 10000 unique IPs tracked
- Prunes oldest entries when limit exceeded
- Protects against memory exhaustion DoS

Test count: 284 -> 291 (7 new security tests)
2025-12-24 21:42:15 +01:00

7.6 KiB

FlaskPaste Deployment Guide

Overview

FlaskPaste can be deployed in several ways:

  • Development server (for testing only)
  • WSGI server (Gunicorn, uWSGI)
  • Container (Podman/Docker)

Prerequisites

  • Python 3.11+
  • SQLite 3
  • Reverse proxy for TLS termination (nginx, Apache, Caddy)

Development Server

For testing only - not for production!

# Setup
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Run
python run.py
# or
flask --app app run --debug

Production: WSGI Server

Gunicorn

pip install gunicorn

# Basic usage
gunicorn -w 4 -b 0.0.0.0:5000 wsgi:app

# With Unix socket (recommended for nginx)
gunicorn -w 4 --bind unix:/run/flaskpaste/gunicorn.sock wsgi:app

# Production settings
gunicorn \
  --workers 4 \
  --bind unix:/run/flaskpaste/gunicorn.sock \
  --access-logfile /var/log/flaskpaste/access.log \
  --error-logfile /var/log/flaskpaste/error.log \
  --capture-output \
  wsgi:app

Systemd Service

Use the provided examples in examples/:

# Create service user
sudo useradd -r -s /sbin/nologin flaskpaste

# Copy application
sudo mkdir -p /opt/flaskpaste/data
sudo cp -r . /opt/flaskpaste/
sudo chown -R flaskpaste:flaskpaste /opt/flaskpaste

# Copy service unit (with security hardening)
sudo cp examples/flaskpaste.service /etc/systemd/system/

# Copy and secure environment file
sudo mkdir -p /etc/flaskpaste
sudo cp examples/flaskpaste.env /etc/flaskpaste/env
sudo chmod 600 /etc/flaskpaste/env

# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable --now flaskpaste

The provided service unit includes security hardening:

  • NoNewPrivileges, PrivateTmp, ProtectSystem=strict
  • MemoryDenyWriteExecute, RestrictNamespaces
  • Resource limits and restart policies

See examples/flaskpaste.service for the full configuration.


Production: Container Deployment

Build the Container

# Using Podman
podman build -t flaskpaste .

# Using Docker
docker build -t flaskpaste .

Run the Container

# Basic run
podman run -d \
  --name flaskpaste \
  -p 5000:5000 \
  -v flaskpaste-data:/app/data \
  flaskpaste

# With environment configuration
podman run -d \
  --name flaskpaste \
  -p 5000:5000 \
  -v flaskpaste-data:/app/data \
  -e FLASKPASTE_EXPIRY=604800 \
  -e FLASKPASTE_MAX_ANON=1048576 \
  flaskpaste

Podman Quadlet (systemd integration)

Create /etc/containers/systemd/flaskpaste.container:

[Unit]
Description=FlaskPaste Container
After=local-fs.target

[Container]
Image=localhost/flaskpaste:latest
ContainerName=flaskpaste
PublishPort=5000:5000
Volume=flaskpaste-data:/app/data:Z
Environment=FLASK_ENV=production
Environment=FLASKPASTE_EXPIRY=432000

[Service]
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target default.target
systemctl --user daemon-reload
systemctl --user start flaskpaste

Reverse Proxy Configuration

nginx

upstream flaskpaste {
    server unix:/run/flaskpaste/gunicorn.sock;
    # or for container: server 127.0.0.1:5000;
}

server {
    listen 443 ssl http2;
    server_name paste.example.com;

    ssl_certificate /etc/ssl/certs/paste.example.com.crt;
    ssl_certificate_key /etc/ssl/private/paste.example.com.key;

    # Optional: Client certificate authentication
    ssl_client_certificate /etc/ssl/certs/ca.crt;
    ssl_verify_client optional;

    # Size limit (should match FLASKPASTE_MAX_AUTH)
    client_max_body_size 50M;

    location / {
        proxy_pass http://flaskpaste;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Pass client certificate fingerprint
        proxy_set_header X-SSL-Client-SHA1 $ssl_client_fingerprint;
    }
}

Caddy

paste.example.com {
    reverse_proxy localhost:5000

    # Client certificate auth (if needed)
    tls {
        client_auth {
            mode request
            trusted_ca_cert_file /etc/ssl/certs/ca.crt
        }
    }
}

HAProxy

frontend https
    bind *:443 ssl crt /etc/haproxy/certs/

    # Route to flaskpaste backend
    acl is_paste path_beg /paste
    use_backend backend-flaskpaste if is_paste

backend backend-flaskpaste
    mode http

    # Strip /paste prefix before forwarding
    http-request replace-path /paste(.*) \1
    http-request set-path / if { path -m len 0 }

    # Request tracing - generate X-Request-ID if not present
    http-request set-header X-Request-ID %[uuid()] unless { req.hdr(X-Request-ID) -m found }
    http-request set-var(txn.request_id) req.hdr(X-Request-ID)
    http-response set-header X-Request-ID %[var(txn.request_id)]

    # Proxy trust secret - proves request came through HAProxy
    http-request set-header X-Proxy-Secret your-secret-value-here

    # Pass client certificate fingerprint (if using mTLS)
    http-request set-header X-SSL-Client-SHA1 %[ssl_c_sha1,hex,lower]

    server flaskpaste 127.0.0.1:5000 check

Data Persistence

Database Location

Default: ./data/pastes.db

Configure via FLASKPASTE_DB:

export FLASKPASTE_DB=/var/lib/flaskpaste/pastes.db

Backup

# SQLite online backup
sqlite3 /var/lib/flaskpaste/pastes.db ".backup '/backup/pastes-$(date +%Y%m%d).db'"

Container Volume

# Create named volume
podman volume create flaskpaste-data

# Backup from volume
podman run --rm \
  -v flaskpaste-data:/app/data:ro \
  -v ./backup:/backup \
  alpine cp /app/data/pastes.db /backup/

Environment Variables

See examples/flaskpaste.env for a complete template.

Variable Default Description
FLASK_ENV development Set to production in production
FLASKPASTE_DB ./data/pastes.db Database file path
FLASKPASTE_ID_LENGTH 12 Paste ID length (hex chars)
FLASKPASTE_MAX_ANON 3145728 Max anonymous paste (bytes)
FLASKPASTE_MAX_AUTH 52428800 Max authenticated paste (bytes)
FLASKPASTE_EXPIRY_ANON 86400 Anonymous paste expiry (1 day)
FLASKPASTE_EXPIRY_UNTRUSTED 604800 Untrusted cert expiry (7 days)
FLASKPASTE_EXPIRY_TRUSTED 2592000 PKI-registered expiry (30 days)
FLASKPASTE_PROXY_SECRET (empty) Shared secret for proxy trust
FLASKPASTE_POW_DIFFICULTY 20 PoW difficulty (0=disabled)
FLASKPASTE_RATE_LIMIT 1 Enable IP-based rate limiting
FLASKPASTE_RATE_MAX 10 Max requests per window (anon)
FLASKPASTE_RATE_AUTH_MULT 5 Multiplier for authenticated users
FLASKPASTE_RATE_MAX_ENTRIES 10000 Max tracked IPs (memory DoS protection)
FLASKPASTE_ANTIFLOOD 1 Enable dynamic PoW under attack
FLASKPASTE_PKI_ENABLED 0 Enable built-in PKI

Security Checklist

  • Run behind reverse proxy with TLS
  • Use non-root user (or systemd security hardening)
  • Set appropriate file permissions on database
  • Configure firewall (only expose reverse proxy)
  • Set FLASK_ENV=production
  • Configure client certificate auth (if needed)
  • Set FLASKPASTE_PROXY_SECRET for defense-in-depth
  • Configure proxy to send X-Proxy-Secret header
  • Configure proxy to pass/generate X-Request-ID
  • Enable rate limiting (FLASKPASTE_RATE_LIMIT=1)
  • Enable anti-flood (FLASKPASTE_ANTIFLOOD=1)
  • Enable PoW (FLASKPASTE_POW_DIFFICULTY=20)
  • Set up log rotation
  • Enable automatic backups
  • Monitor disk usage (paste storage)
  • Monitor rate limit headers (X-RateLimit-*)