s3proxy

module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: Apache-2.0

README

s3proxy

A path-based multi-backend proxy for S3-compatible APIs, written in Go.

The proxy accepts S3-compatible requests, optionally authenticates clients with SigV4, determines one or more destination backends from the incoming request path, bucket name, host, and operation type, rewrites requests as needed, and forwards them to S3-compatible targets using the destinations' credentials.

Current V1 Scope

Supported Operations
  • GetObject
  • HeadObject
  • PutObject
  • DeleteObject
  • HeadBucket
  • ListObjectsV2
  • ListBuckets (virtual/proxy-defined)

Supported v1 operations accept only the query keys needed for their current contract. Inbound SigV4 presign query keys (X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-Security-Token, X-Amz-Signature, and X-Amz-SignedHeaders) are accepted for authentication and are not forwarded to backends.

Operation Supported non-auth query keys
GetObject, HeadObject, PutObject, DeleteObject, HeadBucket, ListBuckets none, except optional AWS SDK x-id matching the operation name
ListObjectsV2 list-type=2, continuation-token, delimiter, encoding-type, fetch-owner, max-keys, prefix, start-after, and optional x-id=ListObjectsV2

All other query-key combinations are rejected before route dispatch. This includes object or bucket subresources such as acl, tagging, retention, legal-hold, torrent, versioning, versions, versionId, restore, select, response header overrides such as response-content-type, and multipart query variants such as uploads, uploadId, and partNumber.

Auth Modes
  • none – skip inbound authentication (trusted environments only)
  • sigv4_static – validate inbound SigV4 signatures with statically configured client credentials
Routing
  • path-style and virtual-hosted addressing
  • parsers: path prefix, bucket exact, bucket regex, host suffix
  • ordered route evaluation with stop / continue match modes
  • destination dispatch modes: first (single target) and all (fan-out)
  • read preferences: first, random, hash, ordered_failover
Rewrite
  • strip path prefix
  • strip / prepend key prefix
  • override bucket name
  • key templates with captures from regex parsers
Unsupported in V1
  • Multipart uploads — return S3-compatible NotImplemented
  • Unsupported S3 subresource operations — return S3-compatible NotImplemented
  • Credential generation / rotation / DB-backed auth
  • Merged multi-backend ListObjects pagination
  • Hot config reload
  • Full presigned URL feature parity

See docs/design.md for the full design document.

Install

via asdf

Add the plugin:

asdf plugin add s3proxy
# or
asdf plugin add s3proxy https://github.com/egose/s3proxy.git

Install and activate a version:

# List all available versions
asdf list all s3proxy

# Install a specific version
asdf install s3proxy <version>

# Install the latest stable version
asdf install s3proxy latest

# Set the global version
asdf global s3proxy <version>

Once installed, the s3proxy binary is available directly on your PATH:

s3proxy serve --config /etc/s3proxy/config.hcl
s3proxy validate --config /etc/s3proxy/config.hcl
s3proxy version

Please check the asdf documentation for more details.

Example Configuration

listener "http" "public" {
  address = ":8080"
  replay_body_max_bytes = 33554432
  replay_body_aggregate_max_bytes = 268435456

  addressing {
    path_style     = true
    virtual_hosted = true
    host_suffixes  = ["s3proxy.example.com"]
  }

  timeouts {
    read_header = "10s"
    idle        = "60s"
    write       = "0s"
  }
}

auth "main" {
  mode = "sigv4_static"

  client "ci" {
    access_key = env("S3PROXY_CLIENT_CI_ACCESS_KEY")
    secret_key = env("S3PROXY_CLIENT_CI_SECRET_KEY")

    allow_routes = [
      "route.images_rw",
    ]

    visible_buckets = ["images"]
  }

  client "admin" {
    access_key = env("S3PROXY_CLIENT_ADMIN_ACCESS_KEY")
    secret_key = env("S3PROXY_CLIENT_ADMIN_SECRET_KEY")

    allow_routes    = ["*"]
    visible_buckets = ["*"]
  }
}

credential "static" "primary" {
  access_key = env("S3PROXY_TARGET_PRIMARY_ACCESS_KEY")
  secret_key = env("S3PROXY_TARGET_PRIMARY_SECRET_KEY")
}

target "s3" "primary" {
  endpoint         = "https://minio-a.internal"
  region           = "us-east-1"
  force_path_style = true
  timeout          = "5s"
  credentials      = "primary"
}

parser "path_prefix" "images" {
  prefix = "/images"
}

parser "bucket_regex" "tenant_logs" {
  pattern = "^tenant-(?P<tenant>[a-z0-9-]+)-logs$"
}

route "images_rw" {
  parser        = "images"
  operations    = ["GetObject", "HeadObject", "PutObject", "DeleteObject", "ListObjectsV2"]
  destinations  = ["primary"]
  dispatch      = "first"
  on_match      = "stop"
  read_preference = "first"

  rewrite {
    strip_path_prefix  = "/images"
    prepend_key_prefix = "assets/"
    bucket             = "images-store"
  }
}

bucket "images" {
  visible_name = "images"
  route        = "images_rw"
}

replay_body_max_bytes limits how much request body the proxy will buffer per request when it needs a replayable body, such as dispatch = "all", on_match = "continue", authenticated payload-hash verification, or unknown-length outbound uploads. 0 uses the built-in default of 33554432 bytes (32 MiB). Oversized replay attempts fail with 413 EntityTooLarge. replay_body_aggregate_max_bytes limits retained replay buffers across the process; 0 uses the built-in default of 268435456 bytes (256 MiB). Aggregate exhaustion fails immediately with 503 SlowDown instead of blocking request goroutines.

CLI

s3proxy serve --config /etc/s3proxy/config.hcl
s3proxy validate --config /etc/s3proxy/config.hcl
s3proxy version

Docker

docker build -t s3proxy .
docker run --rm \
  -p 8080:8080 \
  -v ./config.hcl:/etc/s3proxy/config.hcl:ro \
  -e S3PROXY_CLIENT_CI_ACCESS_KEY=... \
  -e S3PROXY_CLIENT_CI_SECRET_KEY=... \
  -e S3PROXY_TARGET_PRIMARY_ACCESS_KEY=... \
  -e S3PROXY_TARGET_PRIMARY_SECRET_KEY=... \
  s3proxy

Local Run

Create config.hcl then:

go run ./cmd/s3proxy serve --config config.hcl

Tests

go test ./...                                  # unit tests
make vet test                                  # vet + unit tests
make test-race                                # unit tests with the race detector
Integration tests

The integration test suite (internal/integration, build-tagged integration) exercises the proxy end-to-end against the sandbox docker-compose stack: MinIO as the primary backend and SeaweedFS as the replica.

cp .env.example .env                            # then edit secrets if you want
make sandbox-integration-up                    # start stack + proxy + run tests + tear down
# or, to leave the stack up for iterative runs:
make sandbox-up DAEMON=true
make build
make test-integration                          # repeats against the running stack
make sandbox-down
make test-integration-race                      # fan-out path under the race detector

make sandbox-integration-up sources .env into the proxy's environment (so env("...") config substitution resolves), runs the stack + proxy + tests + teardown in one shot, and exits with the test's status.

See sandbox/integration-config.hcl for the routing/parsers/credentials configuration the suite expects.

Environment Variables

Use env("VAR") in any string attribute in the HCL config to inline an environment variable. This is necessary for secrets — do not commit secret values into the config file.

Notes on Behavior

  • key_template uses Go template syntax. Accessed data is keyed by Bucket, Key, and Captures (a map[string]string of regex named-group captures).
  • Escaped path bytes are preserved through routing and rewrites, so keys such as %2F stay distinct from literal path separators.
  • For multi-destination routes, read_preference controls which destination is used for reads (first by default).
  • For multi-destination writes with dispatch = "all", all destinations must succeed. Upstream HTTP failures return the primary upstream error response; transport or replay failures return a proxy error. Fan-out is not transactional: a destination that succeeds before another destination fails is not rolled back.
  • For multi-route writes with on_match = "continue", every matched route must succeed; a later route failure cannot be hidden by an earlier success.
  • ordered_failover only fails over on transport errors, timeouts, and upstream 5xx. It does not fail over on 404 / NoSuchKey / NoSuchBucket responses.
  • target "s3" supports an optional timeout duration. For ordered_failover, this directly bounds how long the proxy waits before failing over from a slow or unreachable backend.
  • ListBuckets returns proxy-defined virtual buckets, not upstream discovery.
  • Request body replay for multi-destination writes, multi-route writes, payload-hash verification, and unknown-length outbound uploads is bounded per request by listener.replay_body_max_bytes and across the process by listener.replay_body_aggregate_max_bytes. Per-request overflow fails with 413 EntityTooLarge; aggregate exhaustion fails with 503 SlowDown.

Deferred / Planned

See the "Deferred Features" section in docs/design.md for a full list of intentionally deferred features, including multipart upload support and dynamic credential generation.

Directories

Path Synopsis
cmd
s3proxy command
internal
app

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL