# 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!** ```bash # 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 ```bash 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/`: ```bash # 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 ```bash # Using Podman podman build -t flaskpaste . # Using Docker docker build -t flaskpaste . ``` ### Run the Container ```bash # 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`: ```ini [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 ``` ```bash systemctl --user daemon-reload systemctl --user start flaskpaste ``` --- ## Reverse Proxy Configuration ### nginx ```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 ```caddyfile 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 ```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`: ```bash export FLASKPASTE_DB=/var/lib/flaskpaste/pastes.db ``` ### Backup ```bash # SQLite online backup sqlite3 /var/lib/flaskpaste/pastes.db ".backup '/backup/pastes-$(date +%Y%m%d).db'" ``` ### Container Volume ```bash # 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-*`)