S3 Encryption Proxy¶
The CloudTaser S3 proxy provides transparent client-side encryption for S3-compatible object storage. It runs as a sidecar container, intercepting S3 API calls to encrypt objects on upload and decrypt them on download. AWS (or any S3-compatible provider) only ever stores ciphertext.
The S3 proxy ensures that all data stored in S3 is encrypted with keys held exclusively in the EU-hosted OpenBao/Vault Transit engine. Even if AWS receives a CLOUD Act warrant, the stored objects are ciphertext that cannot be decrypted without the EU-hosted key encryption key (KEK).
How It Works¶
The proxy sits between the application and the S3 endpoint as a reverse proxy:
Application (localhost:8190) -> CloudTaser S3 Proxy -> S3 Endpoint (e.g., s3.eu-west-1.amazonaws.com)
- Application sends S3 requests to localhost:8190
- On
PutObject: proxy generates a random DEK, encrypts the object body with AES-256-GCM, wraps the DEK via Vault Transit, stores the wrapped DEK and nonce as S3 object metadata - On
GetObject: proxy fetches the object from S3, reads the wrapped DEK from metadata, unwraps it via Vault Transit, decrypts the body, returns plaintext to the application - On
HeadObject: proxy passes through and returns metadata (including encryption metadata headers) - Non-encryption operations (
ListObjects,DeleteObject, etc.) pass through unmodified - All upstream requests are re-signed with SigV4 using the pod's AWS credentials (IRSA)
Supported Operations¶
| Operation | Encryption Behavior |
|---|---|
PutObject |
Encrypt body, wrap DEK, add metadata headers |
GetObject |
Fetch from S3, unwrap DEK, decrypt body |
HeadObject |
Pass through (returns metadata including encryption headers) |
UploadPart (multipart) |
Encrypt each part independently |
CreateMultipartUpload |
Pass through |
CompleteMultipartUpload |
Pass through |
AbortMultipartUpload |
Pass through |
ListObjectsV2 |
Pass through |
DeleteObject |
Pass through |
| All other operations | Pass through |
Encryption Protocol¶
- Algorithm: AES-256-GCM
- Per-object random DEK (32 bytes) and nonce (12 bytes)
- DEK wrapped by Vault Transit engine -- the KEK never leaves the EU vault
- Metadata storage: Encrypted DEK, nonce, and plaintext size stored as S3 object metadata headers (
x-amz-meta-cloudtaser-*) - Multipart uploads: Each part is encrypted independently with its own DEK and nonce
Configuration¶
The proxy is configured via environment variables. When deployed as a sidecar by the CloudTaser operator, these are set automatically from pod annotations.
Required Environment Variables¶
| Variable | Description | Default |
|---|---|---|
CLOUDTASER_S3PROXY_S3_ENDPOINT |
Upstream S3 endpoint URL | -- (required) |
CLOUDTASER_S3PROXY_S3_REGION |
AWS region | -- (required) |
VAULT_ADDR |
OpenBao/Vault address | -- (required) |
CLOUDTASER_S3PROXY_TRANSIT_KEY |
Vault Transit engine key name | -- (required) |
Optional Environment Variables¶
| Variable | Description | Default |
|---|---|---|
CLOUDTASER_S3PROXY_LISTEN_ADDR |
Proxy listen address | :8190 |
CLOUDTASER_S3PROXY_TRANSIT_MOUNT |
Vault Transit engine mount path | transit |
CLOUDTASER_S3PROXY_HEALTH_ADDR |
Health check listen address | :8191 |
CLOUDTASER_S3PROXY_MAX_OBJECT_SIZE |
Maximum object size for single PutObject (bytes) | 268435456 (256 MiB) |
VAULT_TOKEN |
Vault token (development/testing only) | -- |
VAULT_AUTH_METHOD |
Auth method: kubernetes or token |
kubernetes |
VAULT_AUTH_ROLE |
Kubernetes auth role name | -- |
VAULT_AUTH_MOUNT_PATH |
Auth mount path | kubernetes |
VAULT_SKIP_VERIFY |
Skip TLS verification for Vault (development only) | false |
Deployment via Operator¶
The operator injects the S3 proxy sidecar when the cloudtaser.io/s3-proxy: "true" annotation is set on the pod. The proxy listens on port 8190 with a health check on port 8191.
Pod Annotations¶
| Annotation | Description | Required |
|---|---|---|
cloudtaser.io/s3-proxy |
Set to "true" to enable S3 proxy sidecar |
Yes |
cloudtaser.io/s3-proxy-endpoint |
Upstream S3 endpoint URL | Yes |
cloudtaser.io/s3-proxy-region |
AWS region | Yes |
cloudtaser.io/s3-proxy-transit-key |
Vault Transit key name | Yes |
cloudtaser.io/s3-proxy-transit-mount |
Vault Transit mount path | No (default: transit) |
Example¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
metadata:
annotations:
cloudtaser.io/inject: "true"
cloudtaser.io/vault-address: "https://vault.eu.example.com:8200"
cloudtaser.io/vault-role: "myapp"
cloudtaser.io/secret-paths: "secret/data/myapp/config"
cloudtaser.io/env-map: "api_key=API_KEY"
cloudtaser.io/s3-proxy: "true"
cloudtaser.io/s3-proxy-endpoint: "https://s3.eu-west-1.amazonaws.com"
cloudtaser.io/s3-proxy-region: "eu-west-1"
cloudtaser.io/s3-proxy-transit-key: "myapp-s3"
spec:
containers:
- name: myapp
env:
- name: AWS_S3_ENDPOINT
value: "http://localhost:8190" # Point S3 client to the proxy
Vault Transit Setup¶
The proxy requires a Vault Transit engine with a key for DEK wrapping:
# Enable Transit engine (if not already enabled)
vault secrets enable transit
# Create a key for the S3 proxy
vault write -f transit/keys/myapp-s3 type=aes256-gcm96
# Grant the CloudTaser role access to the key
vault policy write cloudtaser-s3 - <<EOF
path "transit/encrypt/myapp-s3" {
capabilities = ["update"]
}
path "transit/decrypt/myapp-s3" {
capabilities = ["update"]
}
EOF
AWS Credentials¶
The proxy re-signs all requests with SigV4 before forwarding to the upstream S3 endpoint. AWS credentials must be available to the proxy. The recommended approach is IAM Roles for Service Accounts (IRSA):
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: [email protected]
See S3 Proxy Credentials for detailed setup instructions.
Validation¶
The proxy validates all required configuration at startup and exits with an error if any is missing:
S3Endpoint(CLOUDTASER_S3PROXY_S3_ENDPOINT)S3Region(CLOUDTASER_S3PROXY_S3_REGION)VaultAddr(VAULT_ADDR)VaultTransitKey(CLOUDTASER_S3PROXY_TRANSIT_KEY)
The auth method must be "token" or "kubernetes". When using "token", VAULT_TOKEN is also required. MaxObjectSize must be positive.