Skip to content

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.

env:
  - name: CLOUDTASER_S3PROXY_LISTEN_ADDR
    value: ":8099"

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.

env:
  - name: S3_ENDPOINT
    value: "https://s3.eu-west-1.amazonaws.com"

S3_REGION

Required No
Default eu-west-1

The AWS region for S3 requests. Used for request signing.

env:
  - name: S3_REGION
    value: "eu-central-1"

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.

env:
  - name: VAULT_ADDR
    value: "https://vault.eu-west-1.example.com:8200"

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.

env:
  - name: CLOUDTASER_S3PROXY_TRANSIT_KEY
    value: "s3-dek-wrapper"

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.

env:
  - name: TRANSIT_MOUNT
    value: "transit"

Health and Limits

CLOUDTASER_S3PROXY_HEALTH_ADDR

Required No
Default :8098

The address for the proxy's health check endpoint.

  • GET /healthz -- Returns 200 OK when the proxy is running and can reach Vault.
  • GET /readyz -- Returns 200 OK when 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.

env:
  - name: MAX_OBJECT_SIZE
    value: "5Gi"

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)
import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:8099",
)
const { S3Client } = require("@aws-sdk/client-s3");

const client = new S3Client({
    endpoint: "http://localhost:8099",
    forcePathStyle: true,
});
S3Client s3 = S3Client.builder()
    .endpointOverride(URI.create("http://localhost:8099"))
    .forcePathStyle(true)
    .build();
# Works with any AWS SDK that respects this variable
export AWS_ENDPOINT_URL=http://localhost:8099

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.