Files
flaskpaste/documentation/deployment.md
2025-12-24 20:05:30 +01:00

319 lines
7.5 KiB
Markdown

# 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_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-*`)