Transparent client-side encryption for S3. Zero code changes.
S3's server-side encryption is great, but your cloud provider holds the keys. S3Proxy sits between your app and S3, encrypting everything before it leaves your infrastructure.
┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ plain │ │ AES │ │
│ Your App │ ──────▶ │ S3Proxy │ ──────▶ │ S3 │
│ │ data │ │ 256 │ │
└──────────┘ └──────────┘ └──────────┘
│
You own the keys.
Each AWS credential is configured with its own encryption key (KEK). The proxy verifies the client's signature with the credential's secret key, then encrypts/decrypts that credential's objects with its KEK.
Option A — inline secrets (quick start):
helm install s3proxy oci://ghcr.io/serversidehannes/s3proxy-python/charts/s3proxy-python \
--set secrets.credentials[0].accessKey="AKIA..." \
--set secrets.credentials[0].secretKey="wJalr..." \
--set secrets.credentials[0].kek="this-credentials-encryption-secret"Option B — existing K8s secret (recommended for production):
kubectl create secret generic s3proxy-secrets \
--from-literal=S3PROXY_CREDENTIALS='[{"access_key":"AKIA...","secret_key":"wJalr...","kek":"this-credentials-encryption-secret"}]'
helm install s3proxy oci://ghcr.io/serversidehannes/s3proxy-python/charts/s3proxy-python \
--set secrets.existingSecrets.enabled=true \
--set secrets.existingSecrets.name=s3proxy-secretsThen point any S3 client at the proxy:
aws s3 --endpoint-url http://s3proxy-python:4433 cp file.txt s3://bucket/Use the same credentials you configured above. That's it.
Endpoints — In-cluster:
http://s3proxy-python.<ns>:4433· Front proxy (even load distribution,frontproxy.enabled=true):http://s3proxy-python-frontproxy.<ns>· Outside the cluster: enableingressin front of the front proxyHealth —
GET /healthz·GET /readyz· Metrics —GET /metrics
Verified with real database operators: backup, cluster delete, restore, data integrity check.
| Database | Operator | Backup Tool |
|---|---|---|
| PostgreSQL 17 | CloudNativePG 1.25 | Barman S3 |
| Elasticsearch 9.x | ECK 3.2.0 | S3 Snapshots |
| ScyllaDB 6.x | Scylla Operator 1.19 | Scylla Manager |
| ClickHouse 24.x | Altinity Operator | clickhouse-backup |
Credential flow — S3 clients sign requests with their secret key. When S3Proxy encrypts the payload, the body changes and the original signature is invalidated. The proxy re-signs with the same key. Configure credentials once on the proxy, all clients use them.
Envelope encryption — Your master key derives a KEK (Key Encryption Key). Each object gets a random DEK (Data Encryption Key), encrypted with AES-256-GCM. The DEK is wrapped by the KEK and stored as object metadata. Your master key never touches S3.
Master Key → KEK (derived via SHA-256)
└→ wraps DEK (random per object)
└→ encrypts data (AES-256-GCM)
Per-credential keys — Each AWS credential has its own KEK. The proxy verifies the client's signature with the credential's secret key, then wraps that credential's DEKs with the credential's KEK. So a leaked KEK only exposes the data written by that one credential. The access key that wrapped each object is recorded in the object's metadata (isec-kid), so decryption always uses the key that actually encrypted the object — reconfiguring credentials never orphans existing data, as long as that access key's KEK is still present.
# Each credential: access_key + secret_key + its own kek (SHA-256'd into the KEK)
S3PROXY_CREDENTIALS='[
{"access_key":"AKIA-ACME","secret_key":"...","kek":"acme-kek-secret"},
{"access_key":"AKIA-GLOBEX","secret_key":"...","kek":"globex-kek-secret"}
]'A request signed by an access key with no configured KEK is rejected. Via Helm: set secrets.credentials (see chart/values.yaml).
| Value | Default | Description |
|---|---|---|
replicaCount |
3 |
Pod replicas |
s3.host |
s3.amazonaws.com |
S3 endpoint (AWS, MinIO, R2, etc.) |
s3.region |
us-east-1 |
AWS region |
secrets.credentials |
[] |
AWS credentials, each {accessKey, secretKey, kek} |
secrets.existingSecrets.enabled |
false |
Use existing K8s secret |
admin.secret |
change-me |
Secret signing admin session cookies (when admin UI on) |
redis.enabled |
true |
Deploy the bundled single Redis pod (transient upload state) |
frontproxy.enabled |
false |
Bundled HAProxy for even load distribution (no external dependency) |
ingress.enabled |
false |
Expose S3 outside the cluster via Ingress (requires frontproxy.enabled) |
performance.memoryLimitMb |
64 |
Memory budget for streaming concurrency |
See chart/README.md for all options.
Can I use existing unencrypted data?
Yes. S3Proxy detects unencrypted objects and returns them as-is. Migrate by copying through the proxy.What if I lose an encryption key?
Data written by that credential is unrecoverable. Each object records the access key that encrypted it, so keep every credential'skek as long as objects written by that credential exist. Back up your keys.
What if Redis fails mid-upload?
Only in-flight multipart uploads are affected — they fail and must restart. No committed object is ever at risk, since Redis holds only transient, TTL'd upload state. For extra resilience, pointexternalRedis.url at your own HA Redis and set redis.enabled=false.
MinIO / R2 / Spaces?
Yes. Sets3.host to your endpoint.
Presigned URLs?
Yes. The proxy verifies the presigned signature, then makes its own authenticated request to S3.- Key rotation (re-encrypt objects with a new master key)
- Multiple AWS credential pairs (per-client auth)
- Per-credential encryption keys
- S3 Select passthrough
- Ceph S3 compatibility > 80%
- Batch re-encryption CLI tool
- Audit logging (who accessed what, when)
- Web dashboard for key & upload status
- Modular handler architecture (
objects/,multipart/,routing/,client/,streaming/) - Memory-based concurrency limiting (replaces count-based), default 64 MB budget
- Redis state management with automatic recovery; fix data loss on multipart complete/retry
- Hardened input validation, XML escaping, backpressure, and error handling
- Helm chart restructured (
manifests/→chart/) with PDB, standardized labels, and config reference - E2E tests for PostgreSQL, Elasticsearch, ScyllaDB, ClickHouse, and S3 compatibility
- CI workflows for ruff linting and unit tests
- Prometheus-compatible metrics endpoint
- Slimmer Dockerfile and Makefile improvements
MIT