https://github.com/moriyoshi/s3-router
S3 reverse proxy supporting complex routing
https://github.com/moriyoshi/s3-router
aws aws-s3 proxy routing s3 secrets-manager
Last synced: 5 months ago
JSON representation
S3 reverse proxy supporting complex routing
- Host: GitHub
- URL: https://github.com/moriyoshi/s3-router
- Owner: moriyoshi
- License: mit
- Created: 2026-01-20T07:35:31.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2026-01-30T16:47:44.000Z (5 months ago)
- Last Synced: 2026-01-30T21:48:47.798Z (5 months ago)
- Topics: aws, aws-s3, proxy, routing, s3, secrets-manager
- Language: Go
- Homepage:
- Size: 300 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# s3-router
s3-router is a S3 proxy server that intelligently routes requests to multiple S3-compatible backends based on flexible regex patterns and key transformations.
## Features
* **S3 API Compatibility** - S3 API compatibility for object operations (GET, PUT, DELETE, etc.), multipart uploads, and bucket operations
* **Flexible Request Routing** - Route requests based on ordered regex patterns on object keys with deterministic rewrites
* **Request Rewriting** - Multi-step rewrite rules with named and numbered capture group templates for transforming object keys
* **Multiple Backend Support** - Proxy requests to multiple S3-compatible backends with independent configuration and credentials
* **Virtual Host Support** - S3 virtual-hosted-style requests with explicit host mappings and wildcard/suffix matching
* **Advanced Credential Management** - Support for multiple credential sources:
- AWS SDK default credential chain (IAM roles, environment variables, shared credentials)
- File-based credentials with JSON format
- AWS Secrets Manager integration for centralized secret management
- STS role assumption for cross-account access
- Inline static credentials (not recommended for production)
* **SigV4 Authentication** - Full AWS SigV4 signature verification with support for header-based and query-based presigned URLs
* **Circuit Breaker Pattern** - Automatic failure detection and backend isolation with automatic recovery
* **Request/Response Streaming** - Optimized streaming for large file uploads and downloads with minimal memory usage
* **Route Caching** - LRU cache for route matching decisions to improve performance
* **Observability** - Prometheus metrics for monitoring request latency, throughput, and backend health
* **Health Checks** - Built-in health check endpoints and per-backend health monitoring
* **Configurable Timeouts** - Per-backend and server-level timeout configuration
* **Multi-format Configuration** - Support for YAML, JSON, and TOML configuration files
* **Comprehensive Logging** - Human-friendly and structured JSON logging with automatic format detection
### Configuration language
s3-router loads its routing table from a JSON, YAML, or TOML document with the following structure.
```yaml
backends: # map keyed by backend id
backend1:
endpoint: s3.ap-northeast-1.amazonaws.com # optional override; defaults to AWS endpoint logic
bucket: bucket-1 # required physical bucket name
prefix: prefix1/ # optional prefix prepended to every rewritten key
timeout: 30s # optional per-backend HTTP client timeout (Go duration string)
retries: 3 # optional retry count for AWS SDK requests
credentials: # optional; falls back to AWS default chain when omitted
type: file # credential source type: default, file, aws-secrets-manager, inline
path: /etc/s3router/backend-creds.json # required for type: file
assume_role: # optional STS role assumption
role_arn: arn:aws:iam::1234:role/example
session_name: s3-router # optional; defaults to "s3-router"
duration: 3600 # optional; duration in seconds (default: 3600)
buckets: # supports both list and map formats (see examples below)
foo: # virtual bucket exposed to clients (map key)
routes: # first route that matches wins
- path: ^foo/(?P.*) # required RE2 regex applied to the object key
backend: backend1
rewrite: # optional sequential rewrite steps
- result: $rest # template applies captures from `path`
- pattern: ^special/(.*) # optional secondary regex on current key
result: SPECIAL/$1 # supports numbered/named captures ($1, $rest)
# Server configuration (optional; defaults shown)
server:
read_timeout: 15
write_timeout: 15
idle_timeout: 60
max_body_size: 4294967296
route_cache_size: 1000
# Authentication configuration (optional; defaults shown)
auth:
default_region: us-east-1
clock_skew_leeway: 15m
# Virtual hosts configuration (optional; defaults shown)
virtual_hosts:
hosts: []
features: # optional boolean map (currently unused)
enable_caching: true
enable_metrics: false
credentials_store: credentials.json # optional; path to JSON file with static credentials
```
#### Backends
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `endpoint` | string | no | Custom S3-compatible endpoint with optional template placeholders. Defaults to AWS regional/global endpoint resolution. See [Endpoint Configuration](#endpoint-configuration). |
| `bucket` | string | yes | Physical bucket that stores the objects. |
| `prefix` | string | no | Static prefix prepended before the rewritten key (e.g., `folder/`). |
| `timeout` | string | no | Go duration parsed timeout applied to the backend HTTP client (e.g., `30s`, `2m`). |
| `retries` | int | no | Retry attempts for SDK operations. Defaults to the SDK's standard behavior. |
| `credentials` | object | no | Credential source specification with optional role assumption. See [Credential Sources](#credential-sources). |
#### Endpoint Configuration
The `endpoint` field in backend configuration supports two modes:
**Mode 1: Template-based Endpoint (Custom)**
When the `endpoint` field contains placeholders, s3-router resolves them to construct a custom endpoint URL. This is useful for S3-compatible services (MinIO, Wasabi, DigitalOcean Spaces, etc.) or AWS regions with special requirements.
**Supported Placeholders:**
| Placeholder | Format | Description | Example |
| --- | --- | --- | --- |
| `bucket` | `${bucket}` or `$bucket` | The physical bucket name | `my-bucket` |
| `region` | `${region}` or `$region` | AWS region from backend or client context | `us-west-2` |
| `key` | `${key}` or `$key` | The object key (URL-encoded, preserving `/`) | `folder/myfile.txt` |
| `fips` | `${fips}` or `$fips` | Set to `1` if FIPS is enabled, empty otherwise | `1` or `` |
| `globalEndpoint` | `${globalEndpoint}` or `$globalEndpoint` | Set to `1` if using global endpoint, empty otherwise | `1` or `` |
| `accelerate` | `${accelerate}` or `$accelerate` | Set to `1` if S3 Transfer Acceleration is enabled, empty otherwise | `1` or `` |
| `dualStack` | `${dualStack}` or `$dualStack` | Set to `1` if dual-stack (IPv4/IPv6) is enabled, empty otherwise | `1` or `` |
| `usePathStyle` | `${usePathStyle}` or `$usePathStyle` | Set to `1` if using path-style URLs, empty otherwise | `1` or `` |
**Default Values:**
Placeholders support optional default values using the `${name:-default}` syntax:
```yaml
endpoint: "https://${region:-us-east-1}.example.com/${bucket}/${key}"
```
If `${region}` is empty, `us-east-1` will be used.
**Nested and Conditional Placeholders:**
The template system supports unlimited nesting depth and conditional expansion operators:
1. **Nested defaults**: `${VAR:-${FALLBACK:-default}}`
- Falls back through multiple levels until a value is found
```yaml
endpoint: "https://${custom_host:-${region}.example.com}/${bucket}/${key}"
```
If `custom_host` is unset, uses `${region}.example.com`; if both are unset, uses just the bucket and key path.
2. **Conditional expansion (`:+` operator)**: `${VAR:+expansion}`
- If VAR is set (non-empty), substitutes the expansion; otherwise empty string
```yaml
endpoint: "https://s3${fips:+-fips}.${region}.amazonaws.com/${bucket}/${key}"
```
If `fips` is set to `1`, the endpoint becomes `https://s3-fips.{region}.amazonaws.com/...`; otherwise `https://s3.{region}.amazonaws.com/...`
3. **Complex nesting**: Both operators can be combined with arbitrary nesting depth
```yaml
endpoint: "https://${host:+${host}}${host:-minio.${region:-us-east-1}.local}/${bucket}/${key}"
```
**Scheme Handling:**
If the resolved endpoint URL doesn't start with `http://` or `https://`, the router automatically prepends `https://`.
**Template Examples:**
```yaml
# AWS S3 virtual-host-style (path-style not recommended for most cases)
endpoint: "https://${bucket}.s3.${region}.amazonaws.com/${key}"
# AWS S3 path-style
endpoint: "https://s3.${region}.amazonaws.com/${bucket}/${key}"
# MinIO on custom domain
endpoint: "https://minio.example.com/${bucket}/${key}"
# S3-compatible with conditional FIPS
endpoint: "https://s3${fips:--fips}.${region}.amazonaws.com/${bucket}/${key}"
# With default region
endpoint: "https://s3.${region:-us-east-1}.example.com/${bucket}/${key}"
```
**Mode 2: AWS SDK Default Endpoint (When not using template)**
When `endpoint` is omitted from the backend configuration, s3-router uses the AWS SDK's endpoint resolver. This automatically selects the correct regional or global endpoint based on:
- The `region` field in backend config (if present)
- Endpoint options: `use_path_style`, `use_fips`, `use_global_endpoint`, `use_dual_stack`, `accelerate`
The SDK endpoint resolver supports:
- **Regional endpoints**: `https://s3.{region}.amazonaws.com` or virtual-host style
- **FIPS endpoints**: `https://s3-fips.{region}.amazonaws.com`
- **Dual-stack**: IPv4 and IPv6 support
- **Transfer Acceleration**: `https://s3-accelerate.amazonaws.com` or region-specific accelerate endpoints
```yaml
# Uses SDK resolver with regional endpoint
backends:
backend1:
bucket: my-bucket
region: eu-west-1
# Resolved to: https://s3.eu-west-1.amazonaws.com or virtual-host style
# Uses SDK resolver with FIPS endpoint
backend2:
bucket: my-bucket
region: us-east-1
use_fips: true
# Resolved to: https://s3-fips.us-east-1.amazonaws.com
# Uses SDK resolver with multiple options
backend3:
bucket: my-bucket
region: us-west-2
use_fips: true
use_dual_stack: true
accelerate: true
```
#### Credential Sources
Backend credentials are specified using the `credentials` field, which supports multiple source types. When credentials are omitted entirely, the AWS SDK default credential chain is used (environment variables, shared credentials file, IMDS, etc.).
##### Credential Source Types
**`type: default` - AWS SDK Default Credential Chain**
Uses the AWS SDK default credential chain, which includes:
- Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`)
- Shared credentials file (`~/.aws/credentials`)
- Shared config file (`~/.aws/config`)
- IMDSv2 (EC2 instance metadata)
- ECS task credentials
- WebIdentity token (for EKS, OIDC)
```yaml
credentials:
type: default
```
**`type: inline` - Inline Static Credentials**
> ⚠️ **Not Recommended**: Embedding credentials in configuration files is a security risk. Use `default`, `file`, or `aws-secrets-manager` instead.
Embeds credentials directly in the configuration file.
```yaml
credentials:
type: inline
access_key_id: AKIA...
secret_access_key: ...
session_token: optional_temporary_token # optional
```
**`type: file` - Load from JSON File**
Loads credentials from a JSON file on the filesystem. Useful for containerized deployments with mounted secrets or Kubernetes mounted ConfigMaps/Secrets.
```yaml
credentials:
type: file
path: /etc/s3router/backend-creds.json
```
The JSON file format:
```json
{
"access_key_id": "AKIA...",
"secret_access_key": "...",
"session_token": "optional_temporary_token"
}
```
**`type: aws-secrets-manager` - Load from AWS Secrets Manager**
Loads credentials from an AWS Secrets Manager secret. The secret must contain a JSON object with the same format as the file provider.
```yaml
credentials:
type: aws-secrets-manager
secret_name: s3-router/my-backend
region: us-east-1 # optional; defaults to us-east-1
```
The secret value in AWS Secrets Manager:
```json
{
"access_key_id": "AKIA...",
"secret_access_key": "...",
"session_token": "optional_temporary_token"
}
```
##### STS Role Assumption
All credential sources support optional STS role assumption, enabling cross-account or cross-role access patterns:
```yaml
credentials:
type: file
path: /etc/s3router/base-creds.json
assume_role:
role_arn: arn:aws:iam::123456789:role/S3RouterRole
session_name: s3-router-backend1 # optional; defaults to "s3-router"
duration: 3600 # optional; duration in seconds (default: 3600)
```
The flow:
1. Load base credentials from the specified source
2. Use the base credentials to call STS `AssumeRole`
3. Return the temporary credentials from the STS response
4. Use temporary credentials for S3 operations
##### Examples
**Example 1: Default AWS SDK Chain**
```yaml
backends:
backend1:
bucket: my-bucket
credentials:
type: default
```
**Example 2: File-based Credentials**
```yaml
backends:
backend2:
bucket: my-bucket
credentials:
type: file
path: /run/secrets/s3-backend-creds.json
```
**Example 3: AWS Secrets Manager**
```yaml
backends:
backend3:
bucket: my-bucket
credentials:
type: aws-secrets-manager
secret_name: prod/s3-router/backend3
region: us-west-2
```
**Example 4: File-based with Role Assumption**
```yaml
backends:
backend4:
bucket: my-bucket
credentials:
type: file
path: /etc/s3router/cross-account-base-creds.json
assume_role:
role_arn: arn:aws:iam::987654321:role/CrossAccountS3Access
session_name: s3-router-cross-account
duration: 7200
```
**Example 5: Inline Credentials (Not Recommended)**
```yaml
backends:
backend5:
bucket: my-bucket
credentials:
type: inline
access_key_id: AKIA...
secret_access_key: ...
```
> ⚠️ **Not Recommended**: Embedding credentials in configuration files is a security risk. Use `type: default`, `type: file`, or `type: aws-secrets-manager` instead.
#### Buckets and routes
* Buckets are evaluated in the order listed. `name` defines the virtual bucket clients address.
* Each route requires a `path` regular expression (RE2 syntax) that runs against the object key portion after the virtual bucket. Named (`(?P...)`) and numbered captures can be referenced in rewrites.
* `backend` points to a backend entry.
* `rewrite` (optional) is a list of sequential rules:
* Every rule must include `result`, a template that can reference `$1`, `$2`, … and named captures like `$rest`.
* When `pattern` is absent, captures come from the route `path` match; when present, the pattern runs on the current key and exposes its own captures.
* Rules run in order, feeding the output of one step into the next.
* Routing decisions are based on the path regex only; HTTP method and headers are not used for matching today.
If no rewrite rules are provided, the original key (plus any backend `prefix`) is forwarded unchanged.
#### Path parsing and escaping
The router treats the request path as URL-escaped text and only decodes it right before backend calls:
* The incoming object key is derived from `r.URL.EscapedPath()`, so percent-encoded bytes remain escaped during routing.
* Virtual-hosted style:
* Bucket is taken from the host (including `virtual_hosts` mappings).
* Object key is the escaped path without the leading `/`.
* Path-style:
* Bucket is the first path segment and is decoded with `url.PathUnescape`.
* Object key is the remainder of the escaped path (still percent-encoded).
* Route matching and rewrite rules operate on the escaped key string, so patterns see `%2F`, `%20`, etc.
* Backend keys are built as `backend.prefix + rewritten_key`.
* For most SDK-backed operations, the router applies a conservative unescape (valid `%XX` sequences are decoded; invalid escapes are preserved as literal `%`). This means `%2F` becomes `/` before the SDK call.
* Streaming PUT/UploadPart requests and copy-source headers use the rewritten key as-is (still URL-escaped) when building the upstream URL/header. Endpoint templates expect `${key}` to already be URL-encoded and to preserve `/`.
#### Features
`features` is an optional map reserved for future toggles. Flags are currently parsed but not used by runtime logic.
#### Server Configuration
The `server` section (optional) allows tuning of HTTP server behavior:
```yaml
server:
read_timeout: 30s # HTTP read timeout (default: 15s)
write_timeout: 30s # HTTP write timeout (default: 15s)
idle_timeout: 60s # HTTP idle timeout (default: 60s)
max_body_size: 4GB # Max request/response body size (default: 4GB)
route_cache_size: 1k # LRU cache size for route matching (default: 1000)
```
**Value Formats:**
- **Timeouts** (`read_timeout`, `write_timeout`, `idle_timeout`):
- Go duration format: `30s`, `2m`, `1h`, `15m` (recommended)
- Raw seconds (backward compatible): `30`, `60`, `3600`
- Default units: seconds when using plain numbers
- **Size** (`max_body_size`):
- With units: `4GB`, `512MB`, `100KB`, `1024B`, `4GiB`, `512MiB`, `100KiB`
- Raw bytes: `4294967296` (default: 4GB)
- Supported units: B, KB, MB, GB, KiB, MiB, GiB (case-insensitive)
- Uses binary units: 1KB = 1024B, 1MB = 1024KB, etc.
- Binary units: 1KiB = 1024B, 1MiB = 1024KiB, 1GiB = 1024MiB
- **Count** (`route_cache_size`):
- With units: `1k` (1000), `1m` (1000000)
- Raw number: `1000` (default: 1000)
- Supported units: k (thousands), m (millions)
| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `read_timeout` | string/int | 15s | HTTP server read timeout. Supports Go duration format (e.g., `30s`, `2m`) or seconds as a number. |
| `write_timeout` | string/int | 15s | HTTP server write timeout. Supports Go duration format or seconds as a number. |
| `idle_timeout` | string/int | 60s | HTTP server idle timeout. Supports Go duration format or seconds as a number. |
| `max_body_size` | string/int | 4GB | Maximum size of request and response bodies. Supports units (GB, MB, KB, B, GiB, MiB, KiB) or raw bytes. Requests exceeding this limit are rejected. |
| `route_cache_size` | string/int | 1000 | LRU cache size for route matching decisions. Supports units (k, m) or raw counts. Larger values improve performance but use more memory. Set to 0 to disable caching. |
#### Authentication Configuration
The `auth` section (optional) controls signature verification behavior:
```yaml
auth:
default_region: us-east-1 # Default AWS region for SigV4 verification (default: us-east-1)
clock_skew_leeway: 15m # Clock skew tolerance in Go duration format (default: 15m)
```
| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `default_region` | string | us-east-1 | Default AWS region used when verifying SigV4 signatures. This region is used when the client doesn't explicitly specify a region in the Authorization header. Clients can override this by including a region in their credential scope. |
| `clock_skew_leeway` | string | 15m | Clock skew tolerance as a Go duration string (e.g., `15m`, `900s`, `1h`). Requests with timestamps outside this range from server time are rejected to prevent replay attacks. AWS default is 15 minutes. |
#### Virtual Hosts Configuration
The `virtual_hosts` section (optional) enables S3 virtual-hosted-style requests (bucket names in host headers). It is active when the `hosts` list is non-empty.
```yaml
virtual_hosts:
hosts: # List of hosts - string (wildcard/suffix) or map for bucket mapping
- api.local: "my-bucket" # Map: explicit bucket mapping (api.local -> my-bucket)
- "localhost:8080": "bucket-1" # Map with explicit port mapping
- "*.s3.localhost" # Wildcard: bucket from subdomain (mybucket.s3.localhost -> mybucket)
- ".example.com" # Suffix: bucket from subdomain (bucket.example.com -> bucket)
- "*.custom.local": "default" # Wildcard with explicit mapping (any.custom.local -> default)
```
**Virtual Host Mode:**
- When a request matches a configured virtual host, the bucket name is determined by the configuration
- **Map entries**: The bucket name is explicitly mapped (e.g., `api.local: "my-bucket"` → bucket `my-bucket`)
- **Wildcard/suffix strings**: The subdomain is extracted as the bucket name (e.g., `mybucket.s3.localhost` → bucket `mybucket`)
- **Wildcards with mapping**: All matching hosts use the explicit bucket name
- **Exact host strings without mapping are ignored** (fall through to path-style resolution)
- This allows S3 clients to use virtual-hosted-style URLs instead of path-style URLs
**Host Matching Rules:**
- Exact matches: `localhost:8080` matches only requests to that exact host:port
- Wildcard matches: `*.example.com` matches `bucket.example.com` but not `foo.bar.example.com`
- Port support: Patterns can include explicit ports; omitting the port matches any port on that host
| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `hosts` | array | empty | List of host entries. Each entry can be a wildcard/suffix string or a map with one key-value pair (host: bucket). Supports wildcards and ports. |
## Running s3-router
### Command-line Flags
s3-router supports the following command-line flags:
| Flag | Type | Default | Description |
| --- | --- | --- | --- |
| `-config` | string | `config.yaml` | Path to configuration file |
| `-credentials` | string | (none) | Path to credentials file (optional) |
| `-listen` | string | `:8080` | Address to listen on for the main S3 API server |
| `-admin` | string | `:9090` | Address to listen on for the admin server |
| `-log-level` | string | `info` | Log level: `debug`, `info`, `warn`, `error` |
| `-log-format` | string | `auto` | Log format: `auto`, `text`, `json`. When `auto`, uses human-friendly text format for terminal output and structured JSON for piped/redirected output |
| `-timeout` | duration | `30s` | Request timeout (e.g., `30s`, `2m`) |
| `-check-route` | string | (none) | Print routing decision for an `s3://bucket/key` and exit |
### Logging
s3-router provides human-friendly logging with automatic format detection:
- **Terminal (interactive)**: Outputs human-readable text format with timestamps and structured key-value pairs
- **Piped/Redirected**: Automatically switches to structured JSON format for easy parsing by log aggregation systems
You can override the automatic detection with the `-log-format` flag:
```bash
# Interactive terminal - auto-detects text format
./s3router -config config.yaml
# Force text format (human-readable)
./s3router -config config.yaml -log-format text
# Force JSON format (structured, for log aggregation)
./s3router -config config.yaml -log-format json
```
### Authentication & Authorization
s3-router implements **SigV4 signature verification** on all S3 operations.
#### Authenticated Operations (Require SigV4 Signature)
All operations require authentication, including:
- **Object Operations**: GetObject, PutObject, DeleteObject, HeadObject, CopyObject
- **Multi-part Operations**: CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload, ListParts
- **Listing Operations**: ListObjects, ListObjectsV2
- **ACL/Tagging Operations**: GetObjectAcl, PutObjectAcl, GetObjectTagging, PutObjectTagging
- **Other Operations**: DeleteObjects, GetObjectAttributes, etc.
**Unauthenticated requests** to these operations are rejected with a 401 Unauthorized response.
#### Unauthenticated Operations (Bypassed for Compatibility)
The following bucket-level operations return fixed responses without requiring authentication:
- **ListBuckets**: Returns the configured virtual buckets (synthetic timestamps)
- **CreateBucket**: Returns `409 BucketAlreadyExists` (no state mutation)
- **DeleteBucket**: Returns `403 BucketNotEmpty` (no state mutation)
These operations are bypassed to support S3 clients that probe for bucket operations during initialization.
#### Static Credential Store
Authentication uses a static credential store loaded from a JSON file mapping access key IDs to secret keys.
**Credentials file format** (`credentials.json`):
```json
{
"AKIAIOSFODNN7EXAMPLE": {
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"enabled": true,
"created_at": "2024-01-01T00:00:00Z"
},
"AKIATESTKEYEXAMPLE01": {
"secret_key": "test_secret_key_12345678901234567890",
"enabled": true
}
}
```
#### Configuration
Credentials file can be specified in two ways:
1. **CLI flag** (takes precedence):
```bash
./s3router -config config.yaml -credentials credentials.json
```
2. **Config file** (`config.yaml`):
```yaml
credentials_store: /etc/s3router/credentials.json
backends:
backend1:
bucket: my-bucket
buckets:
- name: virtual-bucket
routes:
- path: ^(.*)$
backend: backend1
```
#### Signature Verification
s3-router verifies **AWS SigV4 signatures** on incoming requests, supporting:
- **Header-based signatures**: Authorization header with standard AWS SigV4 format
```
Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20240101/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date;x-amz-content-sha256, Signature=abc123...
X-Amz-Date: 20240101T120000Z
X-Amz-Content-Sha256: 5d41402abc4b2a76b9719d911017c592
```
- **Query-based signatures**: Presigned URLs with X-Amz-Signature parameter
```
GET /?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F...&X-Amz-Date=20240101T120000Z&X-Amz-Signature=...
```
##### Multi-Region Support
s3-router automatically extracts the region from the client's credential scope in the Authorization header. This allows clients from any region to use the router:
```yaml
# Client in ap-northeast-1
Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20240101/ap-northeast-1/s3/aws4_request, ...
# Client in eu-west-1
Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20240101/eu-west-1/s3/aws4_request, ...
```
If the client doesn't specify a region in the credential scope, the default region from the `auth` config section is used:
```yaml
auth:
default_region: ap-northeast-1 # Used when client doesn't specify a region
```
#### Security Features
- **Timestamp validation**: Requests with timestamps outside the configured clock skew tolerance (default: ±15m) are rejected to prevent replay attacks. Configure via `auth.clock_skew_leeway` using Go duration format (e.g., `15m`, `900s`).
- **Credential lookup**: Only requests signed with registered access keys are accepted
- **Constant-time comparison**: Signature comparison uses constant-time HMAC comparison to prevent timing attacks
- **Disabled credentials**: Credentials can be marked `"enabled": false` in the JSON file to revoke access
#### Error Responses
Authentication errors return appropriate HTTP status codes:
| Status | Error Code | Reason |
|--------|-----------|--------|
| 401 | MissingAuthenticationToken | No Authorization header or X-Amz-Signature |
| 400 | InvalidAuthHeader | Malformed or missing authorization components |
| 403 | InvalidAccessKeyId | Access key not found in credential store |
| 403 | SignatureDoesNotMatch | Signature verification failed |
| 403 | RequestTimeTooSkewed | Request timestamp too old or too far in future |
#### Using with AWS SDK / CLI
To test s3-router with AWS CLI or SDK:
```bash
# AWS CLI - configure credentials and endpoint
aws configure --profile s3router
# AWS Access Key ID: AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Default region: us-east-1
# Test with AWS CLI
aws --profile s3router --endpoint-url http://localhost:8080 s3 ls s3://virtual-bucket/
# Python boto3 example
import boto3
client = boto3.client(
's3',
endpoint_url='http://localhost:8080',
aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
region_name='us-east-1'
)
response = client.list_objects_v2(Bucket='virtual-bucket')
```
#### Credential Management Best Practices
##### For Client Authentication Credentials (credentials_store)
1. **File permissions**: Restrict credentials file to user/group that runs s3-router
```bash
chmod 600 credentials.json
```
2. **Secrets management**: Use environment variable substitution or secret management tools
```bash
export ROUTER_CREDENTIALS_PATH=/run/secrets/s3router_credentials.json
./s3router -config config.yaml -credentials $ROUTER_CREDENTIALS_PATH
```
3. **Credential rotation**: Update credentials.json without restarting (restart currently required)
```bash
kill
./s3router -config config.yaml -credentials credentials.json
```
4. **Audit logging**: Enable debug logging to see authentication attempts
```bash
./s3router -config config.yaml -credentials credentials.json -log-level debug
```
##### For Backend Credentials
**Recommended approach: Use externalized credential sources instead of inline credentials**
1. **Use `credentials` field with externalized sources**
- File-based: Mount secrets from Kubernetes, Docker secrets, or configuration management
- AWS Secrets Manager: Automatically rotated, centrally managed, IAM-controlled
- Default chain: Leverage IAM roles on EC2/ECS, environment variables, or shared credentials
2. **File-based credentials (`type: file`)**
```bash
# Use restricted permissions
chmod 600 /etc/s3router/backend-creds.json
# Mount via Docker/Kubernetes for easy rotation
docker run -v /secrets/backend-creds.json:/etc/s3router/backend-creds.json ...
```
3. **AWS Secrets Manager (`type: aws-secrets-manager`)**
```yaml
credentials:
type: aws-secrets-manager
secret_name: prod/s3-router/backend1
region: us-east-1
```
Benefits:
- Automatic credential rotation via Lambda
- Centralized secret management
- IAM-based access control
- Audit trail via CloudTrail
- No plaintext credentials in config files
4. **Default AWS Chain (`type: default`)**
```yaml
credentials:
type: default
```
Best for:
- EC2 instances with IAM roles
- ECS tasks with IAM task roles
- Kubernetes pods with IRSA (IAM Roles for Service Accounts)
- Development with `~/.aws/credentials`
5. **STS Role Assumption for Cross-Account Access**
```yaml
credentials:
type: file
path: /etc/s3router/base-creds.json
assume_role:
role_arn: arn:aws:iam::ACCOUNT2:role/S3RouterRole
duration: 3600
```
Use for:
- Cross-account bucket access
- Least-privilege access (assume role with minimal permissions)
- Audit trails (STS calls are logged in CloudTrail)
## Prefix Optimization
s3-router includes an automatic prefix optimization feature that significantly improves performance for patterns with static prefixes. When a route pattern contains a static prefix (e.g., `^folder/(.*)$`), the router extracts this prefix and uses it in S3 ListObjects API calls instead of scanning all objects in the bucket.
### How It Works
1. **Pattern Analysis**: Routes are analyzed to detect static prefixes at the beginning of the pattern
2. **Prefix Extraction**: Static prefixes are extracted and used directly in S3 ListObjects queries with the `prefix` parameter
3. **Automatic Filtering**: Only objects matching the prefix are retrieved, reducing API calls and improving performance
### Example
```yaml
backends:
backend1:
bucket: my-bucket
buckets:
- name: data
routes:
- path: ^logs/(?P.*)$ # Static prefix "logs/"
backend: backend1
```
With this configuration:
- Instead of listing all objects in `my-bucket`, the router queries for objects with prefix `logs/`
- This reduces the result set, improves performance, and lowers S3 API costs
- The router then applies the regex pattern to the filtered results
### Requirements for Optimization
Prefix optimization applies when:
- The pattern starts with literal characters (e.g., `^logs/`)
- The remainder uses only wildcard patterns (e.g., `.*`, `.+`)
- No complex regex constraints follow the static prefix
If a pattern cannot be optimized (e.g., complex regex after the prefix), the router falls back to full bucket scans.
## Streaming and Performance Considerations
### PUT Object Streaming
s3-router optimizes PutObject operations for streaming to minimize memory usage:
#### Streaming Mode (No Checksum Trailers)
When a client sends a PUT request **without** checksum trailers (the common case):
- Request body is streamed directly to the S3 backend **without buffering**
- Memory usage: Constant, regardless of object size
- Best for: Large file uploads
**Typical clients and scenarios:**
- AWS CLI: `aws s3 cp file.bin s3://bucket/path/` ✅ Streamed
- AWS SDK v3 (JavaScript/Python): Standard PUT requests ✅ Streamed
- S3 multipart uploads with non-trailer checksums ✅ Streamed
#### Buffering Mode (With Checksum Trailers)
When a client sends a PUT request **with** checksum trailers (e.g., `x-amz-trailer: x-amz-checksum-sha256`):
- Entire request body must be buffered to read trailers at the end
- Memory usage: = Object size (pre-calculated via Content-Length, or full body if not provided)
- Required because trailers arrive after the body in chunked encoding
**Clients that use checksum trailers:**
- AWS SDK v3 with flexible checksums enabled
- S3Transfer clients with checksum_algorithm configured
- Some S3-compatible clients that support trailer checksums
### Memory Management
For environments with memory constraints:
1. **Verify client doesn't require checksum trailers**
- Most clients don't send trailers by default
- Check if client explicitly requests trailer checksums
2. **Configure max_body_size appropriately**
```yaml
server:
max_body_size: 5GB # Limit memory usage in checksum trailer mode
```
3. **Monitor memory usage**
- In checksum trailer mode, peak memory = largest object size
- Use `-log-level debug` to see which operations buffer trailers
4. **Recommended approach: Upgrade clients to send checksums in headers**
- Avoids trailer buffering entirely
- Example (AWS SDK): Configure flexible checksums with headers-only mode
### Performance Tips
- **Disable route caching if not needed**
```yaml
server:
route_cache_size: 0
```
- **Tune connection pooling for backend clients**
```yaml
backends:
backend1:
timeout: 30s
retries: 3
```
- **Monitor circuit breaker activation**
- Circuit breaker automatically isolates failing backends
- Triggered when failure rate ≥ 60% over ≥3 requests
- Auto-heals after 10 seconds