S3 Proxy Configuration¶
The CloudTaser S3 encryption proxy sits between your application and cloud object storage, transparently encrypting objects before they reach the cloud provider. Data is encrypted with keys held exclusively in your EU-hosted vault -- the cloud provider stores only ciphertext and never has access to the encryption keys.
Environment Variables¶
Proxy Configuration¶
CLOUDTASER_S3PROXY_LISTEN_ADDR¶
| Required | No |
| Default | :8099 |
The address and port on which the S3 proxy listens for incoming S3 API requests. Your application should configure its S3 client to use this address as the endpoint.
S3_ENDPOINT¶
| Required | No |
| Default | AWS S3 default endpoint |
The upstream S3-compatible endpoint that the proxy forwards requests to after encryption. Override this for non-AWS S3-compatible services or specific regional endpoints.
S3_REGION¶
| Required | No |
| Default | eu-west-1 |
The AWS region for S3 requests. Used for request signing.
Vault Connection¶
VAULT_ADDR¶
| Required | Yes |
| Default | -- |
The address of the EU-hosted OpenBao or Vault instance. The proxy connects to the Vault Transit secrets engine to wrap and unwrap per-object data encryption keys.
VAULT_AUTH_METHOD¶
| Required | No |
| Default | kubernetes |
| Values | kubernetes, token |
Authentication method for the Vault connection.
VAULT_AUTH_ROLE¶
| Required | Yes (when VAULT_AUTH_METHOD=kubernetes) |
| Default | -- |
The Vault Kubernetes auth role name.
VAULT_AUTH_MOUNT_PATH¶
| Required | No |
| Default | auth/kubernetes |
The mount path of the Kubernetes auth method in Vault.
Transit Encryption¶
CLOUDTASER_S3PROXY_TRANSIT_KEY¶
| Required | Yes |
| Default | -- |
The name of the Transit encryption key in Vault used to wrap per-object data encryption keys (DEKs). This key must exist in the Vault Transit secrets engine before the proxy starts.
Key type
The Transit key should be of type aes256-gcm96 for optimal performance. The proxy uses this key only for wrapping/unwrapping DEKs, not for encrypting object data directly.
TRANSIT_MOUNT¶
| Required | No |
| Default | transit |
The mount path of the Transit secrets engine in Vault.
Health and Limits¶
CLOUDTASER_S3PROXY_HEALTH_ADDR¶
| Required | No |
| Default | :8098 |
The address for the proxy's health check endpoint.
GET /healthz-- Returns200 OKwhen the proxy is running and can reach Vault.GET /readyz-- Returns200 OKwhen the proxy is ready to accept S3 requests.
MAX_OBJECT_SIZE¶
| Required | No |
| Default | 5Gi |
The maximum object size the proxy will encrypt. Objects larger than this limit are rejected with an error. This is a safety limit to prevent out-of-memory conditions during envelope encryption.
Environment Variable Reference Table¶
| Variable | Required | Default | Description |
|---|---|---|---|
CLOUDTASER_S3PROXY_LISTEN_ADDR |
No | :8099 |
Proxy listen address |
S3_ENDPOINT |
No | AWS default | Upstream S3 endpoint |
S3_REGION |
No | eu-west-1 |
AWS region |
VAULT_ADDR |
Yes | -- | Vault server address |
VAULT_AUTH_METHOD |
No | kubernetes |
Auth method |
VAULT_AUTH_ROLE |
Conditional | -- | K8s auth role |
VAULT_AUTH_MOUNT_PATH |
No | auth/kubernetes |
Auth mount path |
CLOUDTASER_S3PROXY_TRANSIT_KEY |
Yes | -- | Transit encryption key name |
TRANSIT_MOUNT |
No | transit |
Transit engine mount path |
CLOUDTASER_S3PROXY_HEALTH_ADDR |
No | :8098 |
Health endpoint address |
MAX_OBJECT_SIZE |
No | 5Gi |
Max object size for encryption |
Encryption Protocol¶
The S3 proxy uses envelope encryption with AES-256-GCM and Vault Transit key wrapping.
How It Works¶
Application S3 Proxy Vault Transit Cloud Storage
│ │ │ │
│ PutObject(data) │ │ │
├────────────────────►│ │ │
│ │ 1. Generate random DEK │ │
│ ├──────┐ │ │
│ │◄─────┘ │ │
│ │ │ │
│ │ 2. Encrypt data with DEK │ │
│ │ (AES-256-GCM) │ │
│ ├──────┐ │ │
│ │◄─────┘ │ │
│ │ │ │
│ │ 3. Wrap DEK with Transit │ │
│ ├────────────────────────────►│ │
│ │◄────────────────────────────┤ │
│ │ (wrapped DEK) │ │
│ │ │ │
│ │ 4. Store ciphertext + │ │
│ │ wrapped DEK as metadata │ │
│ ├──────────────────────────────────────────────────►│
│ │◄──────────────────────────────────────────────────┤
│ 200 OK │ │ │
│◄────────────────────┤ │ │
Encryption Details¶
| Property | Value |
|---|---|
| Data encryption algorithm | AES-256-GCM |
| Key derivation | Per-object random DEK (256-bit) |
| DEK wrapping | Vault Transit encrypt endpoint |
| Nonce | 96-bit random per encryption operation |
| Authentication tag | 128-bit GCM tag |
| Wrapped DEK storage | S3 object metadata (x-amz-meta-cloudtaser-dek) |
| Nonce storage | S3 object metadata (x-amz-meta-cloudtaser-nonce) |
| Key version tracking | S3 object metadata (x-amz-meta-cloudtaser-key-version) |
Security Properties¶
- Per-object keys -- Each object is encrypted with a unique DEK. Compromising one DEK does not expose other objects.
- DEK never stored in plaintext -- The plaintext DEK exists only in the proxy's memory during the encryption/decryption operation. Only the wrapped (encrypted) DEK is stored alongside the ciphertext.
- Key rotation -- Rotating the Transit key in Vault allows re-wrapping DEKs without re-encrypting object data. The proxy supports transparent key version handling.
- Cloud provider sees only ciphertext -- The S3 endpoint receives already-encrypted data. Neither the cloud provider nor any entity with access to the storage bucket can decrypt the objects.
Supported S3 Operations¶
The proxy intercepts and processes S3 API operations. Some operations involve encryption/decryption, while others are passed through to the upstream endpoint.
Encrypted Operations¶
These operations involve data encryption or decryption by the proxy.
| Operation | Behaviour |
|---|---|
PutObject |
Object body is encrypted with a per-object DEK before upload. Wrapped DEK and nonce are stored as object metadata. |
GetObject |
Object body is decrypted after download. The proxy unwraps the DEK via Vault Transit and decrypts the ciphertext. |
CopyObject |
The source object is decrypted, then re-encrypted with a new DEK for the destination. |
CreateMultipartUpload |
Initiates an encrypted multipart upload. A DEK is generated for the entire upload. |
UploadPart |
Each part is encrypted with the upload's DEK using a part-specific nonce. |
CompleteMultipartUpload |
Finalizes the upload and stores the wrapped DEK in the completed object's metadata. |
Pass-Through Operations¶
These operations do not involve object data and are forwarded to the upstream S3 endpoint without modification.
| Operation | Notes |
|---|---|
HeadObject |
Returns metadata including CloudTaser encryption headers. |
DeleteObject |
Deletes the object (ciphertext + metadata). |
ListBuckets |
Passed through unchanged. |
ListObjects / ListObjectsV2 |
Passed through unchanged. Object sizes reflect ciphertext size. |
CreateBucket |
Passed through unchanged. |
DeleteBucket |
Passed through unchanged. |
GetBucketLocation |
Passed through unchanged. |
HeadBucket |
Passed through unchanged. |
Object size
Encrypted objects are slightly larger than the original plaintext due to the GCM authentication tag and nonce. The overhead is fixed at 28 bytes per object (12-byte nonce + 16-byte auth tag).
Application Configuration¶
To use the S3 proxy, configure your application's S3 client to point to the proxy's listen address instead of the real S3 endpoint.
cfg, _ := config.LoadDefaultConfig(context.TODO(),
config.WithEndpointResolverWithOptions(
aws.EndpointResolverWithOptionsFunc(
func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: "http://localhost:8099",
HostnameImmutable: true,
}, nil
},
),
),
)
client := s3.NewFromConfig(cfg)
Vault Transit Setup¶
Before using the S3 proxy, create the Transit encryption key in your EU-hosted Vault.
# Enable the Transit secrets engine (if not already enabled)
vault secrets enable transit
# Create the encryption key
vault write -f transit/keys/s3-dek-wrapper \
type=aes256-gcm96 \
exportable=false \
allow_plaintext_backup=false
# Create a policy for the S3 proxy
vault policy write s3-proxy-policy - <<EOF
path "transit/encrypt/s3-dek-wrapper" {
capabilities = ["update"]
}
path "transit/decrypt/s3-dek-wrapper" {
capabilities = ["update"]
}
path "transit/keys/s3-dek-wrapper" {
capabilities = ["read"]
}
EOF
# Bind the policy to the Kubernetes auth role
vault write auth/kubernetes/role/s3-proxy \
bound_service_account_names=document-store \
bound_service_account_namespaces=production \
policies=s3-proxy-policy \
ttl=1h
Key security
Set exportable=false and allow_plaintext_backup=false on the Transit key. This ensures the key material can never leave Vault, even through the Vault API. The key exists only within the EU-hosted Vault instance.