forked from claw/flaskpaste
476 lines
16 KiB
Markdown
476 lines
16 KiB
Markdown
# FlaskPaste
|
|
|
|
A lightweight, secure pastebin REST API built with Flask.
|
|
|
|
## Features
|
|
|
|
- **Simple REST API** - Create, retrieve, list, and delete pastes via HTTP
|
|
- **Binary support** - Upload text, images, archives, and other binary content
|
|
- **Automatic MIME detection** - Magic byte detection (images, video, audio, documents, executables, archives)
|
|
- **Client certificate authentication** - mTLS or header-based via reverse proxy
|
|
- **Tiered expiry** - 1 day (anon), 7 days (untrusted), 30 days (trusted PKI)
|
|
- **Size limits** - 3 MiB anonymous, 50 MiB authenticated
|
|
- **Abuse prevention** - Content-hash deduplication throttles spam
|
|
- **Proof-of-work** - Computational puzzles prevent automated abuse
|
|
- **Anti-flood** - Dynamic PoW difficulty increases under attack
|
|
- **Rate limiting** - Per-IP throttling with X-RateLimit-* headers
|
|
- **E2E encryption** - Client-side AES-256-GCM with key in URL fragment
|
|
- **Burn-after-read** - Single-access pastes that auto-delete
|
|
- **Password protection** - PBKDF2-HMAC-SHA256 with 600k iterations
|
|
- **Built-in PKI** - Certificate authority for client certificate issuance
|
|
- **Admin support** - First registered user can manage all pastes
|
|
- **Security headers** - HSTS, CSP, X-Frame-Options, X-Content-Type-Options
|
|
- **CLI client** - Standalone `fpaste` tool with encryption support
|
|
- **Request tracing** - X-Request-ID for log correlation
|
|
- **URL shortener** - `/s/` endpoints for creating, resolving, and managing short URLs
|
|
- **Audit logging** - PKI certificate lifecycle events (issue, revoke, auth failure)
|
|
- **Observability** - Request duration metrics via Prometheus histogram
|
|
- **Minimal dependencies** - Flask + SQLite, optional cryptography for CLI
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# Clone and setup
|
|
git clone <repository>
|
|
cd flaskpaste
|
|
python3 -m venv venv
|
|
source venv/bin/activate
|
|
pip install -r requirements.txt
|
|
|
|
# Run development server
|
|
python run.py
|
|
```
|
|
|
|
## API Endpoints
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| `GET /` | API information and usage |
|
|
| `GET /health` | Health check (returns DB status) |
|
|
| `GET /challenge` | Get proof-of-work challenge |
|
|
| `GET /client` | Download fpaste CLI client |
|
|
| `POST /` | Create a new paste |
|
|
| `GET /<id>` | Retrieve paste metadata |
|
|
| `HEAD /<id>` | Retrieve paste metadata (headers only) |
|
|
| `GET /<id>/raw` | Retrieve raw paste content |
|
|
| `HEAD /<id>/raw` | Retrieve paste headers (no body) |
|
|
| `DELETE /<id>` | Delete paste (requires auth) |
|
|
| `GET /pastes` | List user's pastes (requires auth) |
|
|
| `GET /register/challenge` | Get PoW challenge for registration |
|
|
| `POST /register` | Register and get client certificate |
|
|
| `POST /s` | Create short URL (PoW required) |
|
|
| `GET /s` | List your short URLs (requires auth) |
|
|
| `GET /s/<id>` | Redirect to target URL (302) |
|
|
| `GET /s/<id>/info` | Short URL metadata |
|
|
| `DELETE /s/<id>` | Delete short URL (requires auth) |
|
|
| `GET /pki` | PKI status and CA info |
|
|
| `GET /pki/ca.crt` | Download CA certificate |
|
|
|
|
## Usage Examples
|
|
|
|
### Create a paste (raw text)
|
|
```bash
|
|
curl --data-binary @file.txt http://localhost:5000/
|
|
```
|
|
|
|
### Create a paste (piped input)
|
|
```bash
|
|
echo "Hello, World!" | curl --data-binary @- http://localhost:5000/
|
|
```
|
|
|
|
### Create a paste (JSON)
|
|
```bash
|
|
curl -H "Content-Type: application/json" \
|
|
-d '{"content":"Hello from JSON!"}' \
|
|
http://localhost:5000/
|
|
```
|
|
|
|
### Retrieve paste metadata
|
|
```bash
|
|
curl http://localhost:5000/abc12345
|
|
```
|
|
|
|
### Retrieve raw content
|
|
```bash
|
|
curl http://localhost:5000/abc12345/raw
|
|
```
|
|
|
|
### Delete a paste (requires authentication)
|
|
```bash
|
|
curl -X DELETE \
|
|
-H "X-SSL-Client-SHA1: <your-cert-fingerprint>" \
|
|
http://localhost:5000/abc12345
|
|
```
|
|
|
|
### List user's pastes (requires authentication)
|
|
```bash
|
|
curl -H "X-SSL-Client-SHA1: <your-cert-fingerprint>" \
|
|
http://localhost:5000/pastes
|
|
```
|
|
|
|
### Create a short URL
|
|
```bash
|
|
curl -X POST -H "Content-Type: application/json" \
|
|
-d '{"url":"https://example.com/long/path"}' \
|
|
http://localhost:5000/s
|
|
```
|
|
|
|
### Follow a short URL
|
|
```bash
|
|
curl -L http://localhost:5000/s/AbCdEfGh
|
|
```
|
|
|
|
## CLI Client
|
|
|
|
A standalone command-line client `fpaste` is included. For E2E encryption, install the optional `cryptography` package.
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
# Download from running server
|
|
curl -sS https://paste.example.com/client > fpaste && chmod +x fpaste
|
|
|
|
# Optional: enable encryption support
|
|
pip install cryptography
|
|
```
|
|
|
|
### Basic Usage
|
|
|
|
```bash
|
|
# Create paste from file (encrypts by default)
|
|
./fpaste file.txt
|
|
# Returns: https://paste.example.com/abc123#<key>
|
|
|
|
# Shortcut: file path auto-selects "create" command
|
|
./fpaste secret.txt # Same as: ./fpaste create secret.txt
|
|
|
|
# Create paste from stdin
|
|
echo "Hello" | ./fpaste
|
|
|
|
# Disable encryption (upload plaintext)
|
|
./fpaste -E file.txt
|
|
./fpaste create --no-encrypt file.txt
|
|
|
|
# Create burn-after-read paste (single access, auto-deletes)
|
|
./fpaste -b secret.txt
|
|
|
|
# Create paste with custom expiry (1 hour)
|
|
./fpaste -x 3600 temp.txt
|
|
|
|
# Combine options: encrypted + burn-after-read
|
|
./fpaste -b secret.txt
|
|
|
|
# Get paste content (auto-decrypts if URL has #key fragment)
|
|
./fpaste get "https://paste.example.com/abc123#<key>"
|
|
|
|
# Get paste metadata
|
|
./fpaste get -m abc12345
|
|
|
|
# List your pastes (requires auth)
|
|
./fpaste list
|
|
|
|
# List all pastes (admin only)
|
|
./fpaste list --all
|
|
|
|
# Delete paste (requires auth)
|
|
./fpaste delete abc12345
|
|
|
|
# Delete multiple pastes
|
|
./fpaste delete abc12345 def67890
|
|
|
|
# Delete all pastes (admin only, requires confirmation)
|
|
./fpaste delete --all --confirm 42 # where 42 is expected count
|
|
|
|
# Show server info
|
|
./fpaste info
|
|
|
|
# Register and get client certificate (if server supports it)
|
|
./fpaste register
|
|
# Saves certificate to ~/.config/fpaste/
|
|
|
|
# Register with auto-configuration
|
|
./fpaste register --configure
|
|
# Creates cert files and updates config with fingerprint
|
|
|
|
# Register with custom name
|
|
./fpaste register -n my-laptop --configure
|
|
```
|
|
|
|
### End-to-End Encryption
|
|
|
|
Content is encrypted by default using AES-256-GCM before upload:
|
|
|
|
- Key is generated locally and never sent to server
|
|
- Key is appended to URL as fragment (`#...`) which browsers never transmit
|
|
- Server stores only opaque ciphertext
|
|
- Retrieval auto-detects `#key` fragment and decrypts locally
|
|
- Use `-E` or `--no-encrypt` to disable encryption
|
|
|
|
This provides true zero-knowledge storage: the server cannot read your content.
|
|
|
|
### Configuration
|
|
|
|
Set server URL and authentication via environment or config file:
|
|
|
|
```bash
|
|
# Environment variables
|
|
export FLASKPASTE_SERVER="https://paste.example.com"
|
|
export FLASKPASTE_CERT_SHA1="your-cert-fingerprint"
|
|
|
|
# Or config file (~/.config/fpaste/config)
|
|
server = https://paste.example.com
|
|
cert_sha1 = your-cert-fingerprint
|
|
```
|
|
|
|
### mTLS Client Certificate Authentication
|
|
|
|
The CLI supports mutual TLS (mTLS) for direct client certificate authentication:
|
|
|
|
```bash
|
|
# Environment variables
|
|
export FLASKPASTE_CLIENT_CERT="/path/to/client.crt"
|
|
export FLASKPASTE_CLIENT_KEY="/path/to/client.key"
|
|
export FLASKPASTE_CA_CERT="/path/to/ca.crt" # Optional CA verification
|
|
|
|
# Or config file (~/.config/fpaste/config)
|
|
client_cert = /path/to/client.crt
|
|
client_key = /path/to/client.key
|
|
ca_cert = /path/to/ca.crt
|
|
```
|
|
|
|
When client certificates are configured, fpaste performs mTLS authentication directly with the server (or TLS-terminating proxy). This is more secure than header-based authentication as the certificate is validated at the TLS layer.
|
|
|
|
### Generating Client Certificates
|
|
|
|
The CLI includes a built-in certificate generator for mTLS authentication:
|
|
|
|
```bash
|
|
# Generate EC certificate (recommended, fast key generation)
|
|
./fpaste cert
|
|
# Outputs fingerprint to stdout, details to stderr
|
|
|
|
# Generate and auto-configure fpaste
|
|
./fpaste cert --configure
|
|
# Creates ~/.config/fpaste/client.{crt,key} and updates config
|
|
|
|
# Custom options
|
|
./fpaste cert -a rsa -b 4096 -d 730 -n "my-client" --configure
|
|
# RSA 4096-bit key, 2-year validity, custom common name
|
|
|
|
# Supported curves for EC: secp256r1, secp384r1 (default), secp521r1
|
|
./fpaste cert -c secp256r1 --configure
|
|
```
|
|
|
|
The generated certificate includes:
|
|
- `CLIENT_AUTH` extended key usage for mTLS
|
|
- SHA1 fingerprint compatible with `X-SSL-Client-SHA1` header
|
|
- Self-signed with configurable validity period
|
|
|
|
## Configuration
|
|
|
|
Configuration via environment variables:
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `FLASK_DEBUG` | `0` | Enable debug mode (`1` = enabled) |
|
|
| `FLASKPASTE_DB` | `./data/pastes.db` | SQLite database path |
|
|
| `FLASKPASTE_ID_LENGTH` | `12` | Paste ID length (hex characters) |
|
|
| `FLASKPASTE_MAX_ANON` | `3145728` (3 MiB) | Max paste size for anonymous users |
|
|
| `FLASKPASTE_MAX_AUTH` | `52428800` (50 MiB) | Max paste size for authenticated users |
|
|
| `FLASKPASTE_EXPIRY_ANON` | `86400` (1 day) | Default expiry for anonymous users |
|
|
| `FLASKPASTE_EXPIRY_UNTRUSTED` | `604800` (7 days) | Default expiry for untrusted cert users |
|
|
| `FLASKPASTE_EXPIRY_TRUSTED` | `2592000` (30 days) | Default expiry for trusted (PKI) cert users |
|
|
| `FLASKPASTE_MAX_EXPIRY` | `7776000` (90 days) | Maximum custom expiry allowed |
|
|
| `FLASKPASTE_DEDUP_WINDOW` | `3600` (1 hour) | Dedup throttle window in seconds |
|
|
| `FLASKPASTE_DEDUP_MAX` | `3` | Max identical submissions per window |
|
|
| `FLASKPASTE_PROXY_SECRET` | (empty) | Shared secret for proxy trust validation |
|
|
| `FLASKPASTE_POW_DIFFICULTY` | `20` | PoW difficulty (leading zero bits, 0=disabled) |
|
|
| `FLASKPASTE_POW_TTL` | `300` (5 min) | PoW challenge validity period |
|
|
| `FLASKPASTE_REGISTER_POW` | `24` | Registration PoW difficulty (higher than paste creation) |
|
|
| `FLASKPASTE_POW_SECRET` | (auto) | Secret for signing PoW challenges |
|
|
| `FLASKPASTE_ANTIFLOOD` | `1` | Enable anti-flood (dynamic PoW difficulty) |
|
|
| `FLASKPASTE_ANTIFLOOD_WINDOW` | `60` | Anti-flood measurement window (seconds) |
|
|
| `FLASKPASTE_ANTIFLOOD_THRESHOLD` | `5` | Requests per window before difficulty increase |
|
|
| `FLASKPASTE_ANTIFLOOD_STEP` | `2` | Difficulty bits added per threshold breach |
|
|
| `FLASKPASTE_ANTIFLOOD_MAX` | `28` | Maximum PoW difficulty |
|
|
| `FLASKPASTE_ANTIFLOOD_DECAY` | `60` | Seconds before difficulty decreases |
|
|
| `FLASKPASTE_RATE_LIMIT` | `1` | Enable IP-based rate limiting |
|
|
| `FLASKPASTE_RATE_WINDOW` | `60` | Rate limit window (seconds) |
|
|
| `FLASKPASTE_RATE_MAX` | `10` | Max requests per window (anon) |
|
|
| `FLASKPASTE_RATE_AUTH_MULT` | `5` | Multiplier for authenticated users |
|
|
| `FLASKPASTE_SHORT_ID_LENGTH` | `8` | Short URL ID length (base62 characters) |
|
|
| `FLASKPASTE_SHORT_URL_MAX` | `2048` | Maximum target URL length |
|
|
| `FLASKPASTE_URL_PREFIX` | (empty) | URL prefix for reverse proxy deployments |
|
|
| `FLASKPASTE_MIN_ENTROPY` | `0` | Min entropy bits/byte (0=disabled, 6.0=require encryption) |
|
|
| `FLASKPASTE_MIN_ENTROPY_SIZE` | `256` | Only check entropy for content >= this size |
|
|
| `FLASKPASTE_PKI_ENABLED` | `0` | Enable PKI certificate authority |
|
|
| `FLASKPASTE_PKI_CA_PASSWORD` | (empty) | CA password (required when PKI enabled) |
|
|
| `FLASKPASTE_PKI_CERT_DAYS` | `365` | Client certificate validity (days) |
|
|
|
|
## Authentication
|
|
|
|
FlaskPaste uses client certificate authentication. When deployed behind a reverse proxy (nginx, Apache), configure the proxy to:
|
|
|
|
1. Terminate TLS and validate client certificates
|
|
2. Extract the certificate SHA1 fingerprint
|
|
3. Pass it via the `X-SSL-Client-SHA1` header
|
|
|
|
Example nginx configuration:
|
|
```nginx
|
|
location / {
|
|
proxy_pass http://127.0.0.1:5000;
|
|
proxy_set_header X-SSL-Client-SHA1 $ssl_client_fingerprint;
|
|
}
|
|
```
|
|
|
|
### Trust Levels
|
|
|
|
FlaskPaste distinguishes three trust levels:
|
|
|
|
| Level | Description | Default Expiry |
|
|
|-------|-------------|----------------|
|
|
| Anonymous | No certificate | 1 day |
|
|
| Untrusted | Valid certificate, not registered via PKI | 7 days |
|
|
| Trusted | Certificate registered via `/register` endpoint | 30 days |
|
|
|
|
Authenticated users can:
|
|
- Upload larger pastes (50 MiB vs 3 MiB)
|
|
- Delete their own pastes
|
|
- List their own pastes
|
|
|
|
**Admin users** (first user to register via PKI) can additionally:
|
|
- List all pastes (`GET /pastes?all=1`)
|
|
- Delete any paste
|
|
|
|
## Production Deployment
|
|
|
|
### Using Gunicorn
|
|
```bash
|
|
pip install gunicorn
|
|
gunicorn -w 4 -b 0.0.0.0:5000 wsgi:app
|
|
```
|
|
|
|
### Using Podman/Docker
|
|
```bash
|
|
podman build -t flaskpaste .
|
|
podman run -d -p 5000:5000 -v flaskpaste-data:/app/data flaskpaste
|
|
```
|
|
|
|
See `Containerfile` for container build configuration.
|
|
|
|
### Using Podman Quadlet
|
|
|
|
Quadlet integrates rootless containers with systemd. Deploy as a dedicated user:
|
|
|
|
```bash
|
|
# Create service user and data directory
|
|
useradd -r -m -d /home/flaskpaste -s /sbin/nologin flaskpaste
|
|
mkdir -p /opt/flaskpaste
|
|
chown flaskpaste:flaskpaste /opt/flaskpaste
|
|
|
|
# Build image as flaskpaste user
|
|
sudo -u flaskpaste podman build -t localhost/flaskpaste:latest .
|
|
|
|
# Install Quadlet unit
|
|
mkdir -p /home/flaskpaste/.config/containers/systemd
|
|
cp flaskpaste.container /home/flaskpaste/.config/containers/systemd/
|
|
chown -R flaskpaste:flaskpaste /home/flaskpaste/.config
|
|
|
|
# Enable lingering (start user services at boot)
|
|
loginctl enable-linger flaskpaste
|
|
|
|
# Start service
|
|
systemctl --user -M flaskpaste@ daemon-reload
|
|
systemctl --user -M flaskpaste@ start flaskpaste
|
|
```
|
|
|
|
Manage with systemctl:
|
|
```bash
|
|
systemctl --user -M flaskpaste@ status flaskpaste
|
|
systemctl --user -M flaskpaste@ restart flaskpaste
|
|
journalctl --user -M flaskpaste@ -u flaskpaste
|
|
```
|
|
|
|
### Using systemd
|
|
```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 and environment file
|
|
sudo cp examples/flaskpaste.service /etc/systemd/system/
|
|
sudo mkdir -p /etc/flaskpaste
|
|
sudo cp examples/flaskpaste.env /etc/flaskpaste/env
|
|
sudo chmod 600 /etc/flaskpaste/env
|
|
|
|
# Enable and start service
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now flaskpaste
|
|
```
|
|
|
|
See `examples/` for service unit and configuration templates.
|
|
|
|
## Development
|
|
|
|
### Running Tests
|
|
```bash
|
|
pip install pytest pytest-cov
|
|
pytest tests/ -v
|
|
```
|
|
|
|
### Test Coverage
|
|
```bash
|
|
pytest tests/ --cov=app --cov-report=term-missing
|
|
```
|
|
|
|
### Project Structure
|
|
```
|
|
flaskpaste/
|
|
├── app/
|
|
│ ├── __init__.py # Flask app factory
|
|
│ ├── config.py # Configuration classes
|
|
│ ├── database.py # SQLite management
|
|
│ ├── audit.py # Audit logging for PKI events
|
|
│ ├── metrics.py # Prometheus metrics and histograms
|
|
│ └── api/
|
|
│ ├── __init__.py # Blueprint setup
|
|
│ └── routes.py # API endpoints
|
|
├── tests/ # Test suite (346+ tests)
|
|
├── data/ # SQLite database
|
|
├── run.py # Development server
|
|
├── wsgi.py # Production WSGI entry
|
|
├── Containerfile # Podman/Docker build
|
|
├── compose.yaml # Podman/Docker Compose
|
|
├── flaskpaste.container # Podman Quadlet unit
|
|
└── requirements.txt # Dependencies
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
- **Input validation** - Paste IDs are hex-only, auth headers validated
|
|
- **MIME sanitization** - Content-Type headers are sanitized
|
|
- **SQL injection protection** - Parameterized queries throughout
|
|
- **Ownership enforcement** - Only owners (or admins) can delete pastes
|
|
- **Size limits** - Prevents resource exhaustion attacks
|
|
- **Abuse prevention** - Content-hash deduplication prevents spam flooding
|
|
- **Entropy enforcement** - Optional minimum entropy rejects plaintext uploads
|
|
- **E2E encryption** - Client-side AES-256-GCM, server is zero-knowledge
|
|
- **Burn-after-read** - Single-use pastes for sensitive data
|
|
- **Password protection** - PBKDF2-HMAC-SHA256 with 600k iterations
|
|
- **Security headers** - HSTS, CSP, X-Frame-Options, X-Content-Type-Options
|
|
- **Proof-of-work** - Computational puzzles prevent automated spam
|
|
- **Rate limiting** - Per-IP throttling with X-RateLimit-* headers
|
|
- **Open redirect prevention** - URL shortener allows only http/https schemes with valid host
|
|
- **Request tracing** - X-Request-ID for log correlation
|
|
- **PKI support** - Built-in CA for client certificate issuance
|
|
- **Audit logging** - PKI certificate events for compliance and forensics
|
|
- **Observability** - Prometheus metrics for monitoring and alerting
|
|
|
|
## License
|
|
|
|
MIT
|