|
| 1 | +<p align="center"> |
| 2 | + <img src="https://img.shields.io/badge/encryption-AES--256--GCM-00d4aa?style=for-the-badge" alt="AES-256-GCM"> |
| 3 | + <img src="https://img.shields.io/badge/python-3.11+-3776ab?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.11+"> |
| 4 | + <img src="https://img.shields.io/badge/FastAPI-009688?style=for-the-badge&logo=fastapi&logoColor=white" alt="FastAPI"> |
| 5 | + <img src="https://img.shields.io/badge/S3-compatible-ff9900?style=for-the-badge&logo=amazons3&logoColor=white" alt="S3 Compatible"> |
| 6 | +</p> |
| 7 | + |
| 8 | +<h1 align="center">S3Proxy</h1> |
| 9 | + |
| 10 | +<p align="center"> |
| 11 | + <strong>Transparent encryption for your S3 storage. Zero code changes required.</strong> |
| 12 | +</p> |
| 13 | + |
| 14 | +<p align="center"> |
| 15 | + Drop-in S3 proxy that encrypts everything on the fly with military-grade AES-256-GCM.<br/> |
| 16 | + Your apps talk to S3Proxy. S3Proxy talks to S3. Your data stays yours. |
| 17 | +</p> |
| 18 | + |
| 19 | +--- |
| 20 | + |
| 21 | +## Why S3Proxy? |
| 22 | + |
| 23 | +Most teams store sensitive data in S3. Most of that data? **Unencrypted at the application level.** |
| 24 | + |
| 25 | +S3's server-side encryption is great, but your cloud provider still holds the keys. With S3Proxy, **you** control encryption. Every object is encrypted before it ever touches S3. |
| 26 | + |
| 27 | +``` |
| 28 | +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ |
| 29 | +│ │ │ │ │ │ |
| 30 | +│ Your App │ ──────▶ │ S3Proxy │ ──────▶ │ AWS S3 │ |
| 31 | +│ │ │ (encrypts) │ │ (storage) │ |
| 32 | +│ │ ◀────── │ (decrypts) │ ◀────── │ │ |
| 33 | +└──────────────┘ └──────────────┘ └──────────────┘ |
| 34 | + ▲ │ |
| 35 | + │ │ |
| 36 | + Plain AES-256-GCM |
| 37 | + Data Encrypted |
| 38 | +``` |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## ✨ Features |
| 43 | + |
| 44 | +🔐 **End-to-End Encryption** — AES-256-GCM with per-object keys wrapped via AES-KWP |
| 45 | + |
| 46 | +🔄 **100% S3 Compatible** — Works with any S3 client, SDK, or CLI. No code changes. |
| 47 | + |
| 48 | +⚡ **Blazing Fast** — Async Python with HTTP/2, uvloop, and streaming I/O |
| 49 | + |
| 50 | +📦 **Multipart Support** — Large file uploads just work, encrypted seamlessly |
| 51 | + |
| 52 | +✅ **AWS SigV4 Verified** — Full signature verification for all requests |
| 53 | + |
| 54 | +🏗️ **Production Ready** — Redis-backed state, horizontal scaling, Kubernetes native |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## 🚀 Quick Start |
| 59 | + |
| 60 | +### One-liner with Docker |
| 61 | + |
| 62 | +```bash |
| 63 | +docker run -p 4433:4433 \ |
| 64 | + -e S3PROXY_ENCRYPT_KEY="your-super-secret-key" \ |
| 65 | + -e AWS_ACCESS_KEY_ID="AKIA..." \ |
| 66 | + -e AWS_SECRET_ACCESS_KEY="..." \ |
| 67 | + ghcr.io/<owner>/sseproxy-python:latest |
| 68 | +``` |
| 69 | + |
| 70 | +### Or run locally |
| 71 | + |
| 72 | +```bash |
| 73 | +# Install |
| 74 | +pip install -e . |
| 75 | + |
| 76 | +# Configure |
| 77 | +export S3PROXY_ENCRYPT_KEY="your-super-secret-key" |
| 78 | +export AWS_ACCESS_KEY_ID="AKIA..." |
| 79 | +export AWS_SECRET_ACCESS_KEY="..." |
| 80 | + |
| 81 | +# Run |
| 82 | +python -m s3proxy.main --no-tls |
| 83 | +``` |
| 84 | + |
| 85 | +### Point your app at it |
| 86 | + |
| 87 | +```bash |
| 88 | +# Instead of s3.amazonaws.com, use localhost:4433 |
| 89 | +aws s3 --endpoint-url http://localhost:4433 cp secret.pdf s3://my-bucket/ |
| 90 | + |
| 91 | +# That's it. Your file is now encrypted in S3. |
| 92 | +``` |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## 🏛️ Architecture |
| 97 | + |
| 98 | +S3Proxy uses a **layered key architecture** for maximum security: |
| 99 | + |
| 100 | +| Layer | Key | Purpose | |
| 101 | +|-------|-----|---------| |
| 102 | +| **KEK** | Derived from your master key | Wraps all DEKs | |
| 103 | +| **DEK** | Random per object | Encrypts actual data | |
| 104 | +| **Nonce** | Random/deterministic | Ensures uniqueness | |
| 105 | + |
| 106 | +Your master key never touches S3. DEKs are wrapped and stored as object metadata. Even if someone accesses your bucket, they get nothing but ciphertext. |
| 107 | + |
| 108 | +--- |
| 109 | + |
| 110 | +## ⚙️ Configuration |
| 111 | + |
| 112 | +All settings via environment variables (prefix: `S3PROXY_`): |
| 113 | + |
| 114 | +| Variable | Default | Description | |
| 115 | +|----------|---------|-------------| |
| 116 | +| `ENCRYPT_KEY` | *required* | Your master encryption key | |
| 117 | +| `HOST` | `s3.amazonaws.com` | S3 endpoint | |
| 118 | +| `REGION` | `us-east-1` | AWS region | |
| 119 | +| `PORT` | `4433` | Listen port | |
| 120 | +| `NO_TLS` | `false` | Disable TLS | |
| 121 | +| `REDIS_URL` | `redis://localhost:6379/0` | Redis for multipart state | |
| 122 | +| `MAX_CONCURRENT_UPLOADS` | `10` | Parallel upload limit | |
| 123 | +| `MAX_CONCURRENT_DOWNLOADS` | `10` | Parallel download limit | |
| 124 | +| `LOG_LEVEL` | `INFO` | Logging verbosity | |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +## 🐳 Deploy to Production |
| 129 | + |
| 130 | +### Docker Compose (with Redis) |
| 131 | + |
| 132 | +```bash |
| 133 | +docker-compose -f e2e/docker-compose.e2e.yml up |
| 134 | +``` |
| 135 | + |
| 136 | +### Kubernetes with Helm |
| 137 | + |
| 138 | +```bash |
| 139 | +# Pull from GitHub Container Registry (OCI) |
| 140 | +helm install s3proxy oci://ghcr.io/<owner>/charts/s3proxy-python \ |
| 141 | + --set config.encryptKey="your-key" \ |
| 142 | + --set redis.enabled=true |
| 143 | +``` |
| 144 | + |
| 145 | +The Helm chart includes: |
| 146 | +- 3 replicas by default |
| 147 | +- Redis HA with Sentinel |
| 148 | +- Health checks & readiness probes |
| 149 | +- Configurable resource limits |
| 150 | + |
| 151 | +--- |
| 152 | + |
| 153 | +## 🧪 Testing |
| 154 | + |
| 155 | +```bash |
| 156 | +# Run all tests |
| 157 | +pytest |
| 158 | + |
| 159 | +# With coverage |
| 160 | +pytest --cov=s3proxy |
| 161 | + |
| 162 | +# E2E tests (requires Docker) |
| 163 | +./e2e/test-e2e-fast.sh |
| 164 | +``` |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## 📊 Performance |
| 169 | + |
| 170 | + |
| 171 | + |
| 172 | +*64KB objects, 10 concurrent connections, 3×30s runs. ~60% overhead is primarily from the extra network hop (Client→Proxy→S3) plus encryption.* |
| 173 | + |
| 174 | +S3Proxy is built for throughput: |
| 175 | + |
| 176 | +- **Streaming I/O** — Large files never buffer in memory |
| 177 | +- **HTTP/2** — Connection multiplexing & pooling |
| 178 | +- **uvloop** — 2-4x faster than default asyncio |
| 179 | +- **Horizontal scaling** — Redis-backed state, run N replicas |
| 180 | + |
| 181 | +--- |
| 182 | + |
| 183 | +## 🛡️ Security Model |
| 184 | + |
| 185 | +| Threat | Mitigation | |
| 186 | +|--------|------------| |
| 187 | +| S3 bucket breach | All data encrypted with AES-256-GCM | |
| 188 | +| Key extraction from S3 | DEKs wrapped with KEK, KEK never stored | |
| 189 | +| Request tampering | Full AWS SigV4 signature verification | |
| 190 | +| Replay attacks | Nonce uniqueness per object | |
| 191 | + |
| 192 | +--- |
| 193 | + |
| 194 | +## 🤝 Contributing |
| 195 | + |
| 196 | +PRs welcome! Please include tests for new functionality. |
| 197 | + |
| 198 | +```bash |
| 199 | +# Setup dev environment |
| 200 | +uv sync |
| 201 | + |
| 202 | +# Run tests before submitting |
| 203 | +pytest |
| 204 | +``` |
| 205 | + |
| 206 | +--- |
| 207 | + |
| 208 | +## 📄 License |
| 209 | + |
| 210 | +MIT |
| 211 | + |
| 212 | +--- |
| 213 | + |
| 214 | +<p align="center"> |
| 215 | + <sub>Built with 🔐 by engineers who believe encryption should be easy.</sub> |
| 216 | +</p> |
| 217 | +# s3proxy-python |
0 commit comments