Skip to content

Commit 82894cf

Browse files
committed
Update multiserver smoke docs for default buckets
1 parent 441e3c3 commit 82894cf

5 files changed

Lines changed: 52 additions & 27 deletions

File tree

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,33 @@ If you have isolated Kubernetes clusters, locked-down microservices, or a heavil
4444

4545
The default and recommended deployment is still the isolated single-server topology shown below: public URLs use `/{bucket}/{key}`, tickets publish to `AIR3_NATS_SUBJECT` (default `air3.tickets`), and only the Private Connector has S3 credentials or private S3 network access.
4646

47-
Multi-server mode is opt-in with `AIR3_MULTI_SERVER=true` on the Edge. In this mode, public URLs include a server alias: `/{server}/{bucket}/{key}`. The Edge verifies that the signed URL includes the same server alias, then publishes connector tickets to the routed NATS subject rendered from `AIR3_NATS_SUBJECT_TEMPLATE` (default `air3.{server}`). A connector for that alias sets `AIR3_SERVER_NAME=<server>` and subscribes to the same derived subject. For example, `server=blue` routes `/blue/demo-bucket/file.txt` to NATS subject `air3.blue` by default.
47+
Multi-server mode is opt-in with `AIR3_MULTI_SERVER=true` on the Edge. In this mode, public URLs normally include a server alias and bucket: `/{server}/{bucket}/{key}`. The Edge verifies that the signed URL includes the same server alias, then publishes connector tickets to the routed NATS subject rendered from `AIR3_NATS_SUBJECT_TEMPLATE` (default `air3.{server}`). A connector for that alias sets `AIR3_SERVER_NAME=<server>` and subscribes to the same derived subject. For example, `server=blue` routes `/blue/demo-bucket/file.txt` to NATS subject `air3.blue` by default. If the Edge sets `S3_BLUE_BUCKET=demo-bucket`, signed URLs can use the short form `/blue/dir/object.txt`; the signature still binds the real bucket (`demo-bucket`) even though it is omitted from the path. For aliases with a default bucket, short-form parsing wins, so `/blue/demo-bucket/dir/object.txt` means key `demo-bucket/dir/object.txt` in the default bucket rather than an explicit bucket segment.
4848

4949
```sh
50-
# Sign a multi-server URL for the blue connector alias.
50+
# Sign a full-path multi-server URL for the blue connector alias.
5151
go run ./cmd/signurl \
5252
-base-url https://files.example.com \
5353
-server blue \
5454
-bucket demo-bucket \
5555
-key dir/object.txt \
5656
-secret "$AIR3_SIGNING_SECRET"
5757

58+
# If the Edge has S3_BLUE_BUCKET=demo-bucket, emit /blue/dir/object.txt.
59+
go run ./cmd/signurl \
60+
-base-url https://files.example.com \
61+
-server blue \
62+
-bucket demo-bucket \
63+
-key dir/object.txt \
64+
-secret "$AIR3_SIGNING_SECRET" \
65+
-default-bucket-path
66+
5867
# Connector for that alias; default template renders air3.blue.
5968
AIR3_SERVER_NAME=blue ./bin/private-connector
6069
# Or customize both sides with, for example:
6170
# AIR3_NATS_SUBJECT_TEMPLATE='air3.{server}.tickets'
6271
```
6372

64-
Direct servers are a separate opt-in exception for multi-server deployments. Aliases listed in `AIR3_DIRECT_SERVERS` are fetched by the Edge directly from their per-alias S3 configuration instead of using NATS and a Private Connector. That places S3 credentials and S3 network reachability on the Edge, so use it only when you intentionally accept that different trust boundary.
73+
Direct servers are a separate opt-in exception for multi-server deployments. Aliases listed in `AIR3_DIRECT_SERVERS` are fetched by the Edge directly from their per-alias S3 configuration instead of using NATS and a Private Connector. A direct alias can also set `S3_{SUFFIX}_BUCKET` for short-form URLs, but that default bucket must be present in `S3_{SUFFIX}_ALLOWED_BUCKETS` so the Edge allowlist still validates the real bucket. Direct mode places S3 credentials and S3 network reachability on the Edge, so use it only when you intentionally accept that different trust boundary.
6574

6675
```mermaid
6776
sequenceDiagram
@@ -145,7 +154,7 @@ make e2e
145154

146155
The smoke tests (`make smoke`) automatically verify that signed `GET`/`HEAD` requests work, expired signatures are rejected, missing objects return `404`, and most importantly, that the Edge container *cannot* bypass the system to connect directly to the private S3 server.
147156

148-
For a runnable multi-server example, use `make e2e-multiserver`. It exercises `blue` as a routed alias through NATS plus a Private Connector, and `direct` as an Edge direct-S3 alias using the `deploy/compose.multiserver.yaml` overlay.
157+
For a runnable multi-server example, use `make e2e-multiserver`. It exercises `blue` as a routed alias through NATS plus a Private Connector, and `direct` as an Edge direct-S3 alias using the `deploy/compose.multiserver.yaml` overlay. Both aliases set a demo default bucket and the smoke harness signs short-form URLs for them, while retaining a full-path `green` request to show non-default aliases are unchanged.
149158

150159
## Performance Benchmarks & Transport Recommendations
151160

deploy/compose.multiserver.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ services:
1818
AIR3_MULTI_SERVER: "true"
1919
AIR3_NATS_SUBJECT_TEMPLATE: "air3.{server}"
2020
AIR3_DIRECT_SERVERS: "direct"
21+
S3_BLUE_BUCKET: "demo"
2122
S3_DIRECT_ENDPOINT: "http://direct-s3-proxy:10080"
2223
S3_DIRECT_REGION: "us-east-1"
24+
S3_DIRECT_BUCKET: "demo"
2325
S3_DIRECT_ALLOWED_BUCKETS: "demo"
2426
S3_DIRECT_ACCESS_KEY_ID: "testuser"
2527
S3_DIRECT_SECRET_ACCESS_KEY: "secret"

deploy/scripts/smoke-multiserver.sh

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ sign_url() {
5454
go run ./cmd/signurl -method "$method" -server "$server" -base-url "$BASE_URL" -bucket "$BUCKET" -key "$key" -secret "$SECRET" -expiration "$expiration"
5555
}
5656

57+
sign_default_bucket_url() {
58+
local method=$1
59+
local server=$2
60+
local key=$3
61+
local expiration=${4:-2m}
62+
go run ./cmd/signurl -method "$method" -server "$server" -base-url "$BASE_URL" -bucket "$BUCKET" -key "$key" -secret "$SECRET" -expiration "$expiration" -default-bucket-path
63+
}
64+
5765
status_for() {
5866
local url=$1
5967
curl --silent --show-error --output /dev/null --write-out '%{http_code}' --cacert "$CERT_DIR/dev-ca.crt" "$url"
@@ -137,7 +145,7 @@ check_optional_head() {
137145
wait_for_blue() {
138146
echo "Waiting for edge gateway at $BASE_URL with server '$BLUE_SERVER'..."
139147
local url
140-
url=$(sign_url GET "$BLUE_SERVER" "$KEY" 2m)
148+
url=$(sign_default_bucket_url GET "$BLUE_SERVER" "$KEY" 2m)
141149
for i in $(seq 1 60); do
142150
if [ "$(status_for "$url" || true)" = "200" ]; then
143151
echo "ok: edge gateway is serving routed signed requests"
@@ -166,16 +174,16 @@ fi
166174

167175
wait_for_blue
168176

169-
blue_get_url=$(sign_url GET "$BLUE_SERVER" "$KEY" 2m)
177+
blue_get_url=$(sign_default_bucket_url GET "$BLUE_SERVER" "$KEY" 2m)
170178
assert_body "blue signed GET" "$blue_get_url"
171179

172-
blue_head_url=$(sign_url HEAD "$BLUE_SERVER" "$KEY" 2m)
180+
blue_head_url=$(sign_default_bucket_url HEAD "$BLUE_SERVER" "$KEY" 2m)
173181
assert_head_no_body "blue signed" "$blue_head_url"
174182

175-
direct_get_url=$(sign_url GET "$DIRECT_SERVER" "$KEY" 2m)
183+
direct_get_url=$(sign_default_bucket_url GET "$DIRECT_SERVER" "$KEY" 2m)
176184
assert_body "direct signed GET" "$direct_get_url"
177185

178-
direct_head_url=$(sign_url HEAD "$DIRECT_SERVER" "$KEY" 2m)
186+
direct_head_url=$(sign_default_bucket_url HEAD "$DIRECT_SERVER" "$KEY" 2m)
179187
check_optional_head "direct signed" "$direct_head_url"
180188

181189
mutated_url=${blue_get_url/\/$BLUE_SERVER\//\/$GREEN_SERVER\/}
@@ -192,16 +200,16 @@ echo "Checking routed connector-down isolation behavior..."
192200
run_compose stop -t 2 private-connector-blue >/dev/null
193201
connector_stopped=true
194202

195-
blue_down_url=$(sign_url GET "$BLUE_SERVER" "$KEY" 30s)
203+
blue_down_url=$(sign_default_bucket_url GET "$BLUE_SERVER" "$KEY" 30s)
196204
assert_status "blue connector-down behavior" "504" "$blue_down_url"
197205

198-
direct_while_down_url=$(sign_url GET "$DIRECT_SERVER" "$KEY" 30s)
206+
direct_while_down_url=$(sign_default_bucket_url GET "$DIRECT_SERVER" "$KEY" 30s)
199207
assert_body "direct signed GET while blue connector is stopped" "$direct_while_down_url"
200208

201209
run_compose start private-connector-blue >/dev/null
202210
connector_stopped=false
203211
for i in $(seq 1 30); do
204-
fresh_url=$(sign_url GET "$BLUE_SERVER" "$KEY" 30s)
212+
fresh_url=$(sign_default_bucket_url GET "$BLUE_SERVER" "$KEY" 30s)
205213
if [ "$(status_for "$fresh_url" || true)" = "200" ]; then
206214
echo "ok: blue connector restarted and fresh routed requests work"
207215
echo "Multi-server smoke tests passed"

docs/architecture.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ sequenceDiagram
4040

4141
## Multi-server routed flow
4242

43-
Multi-server mode is enabled with `AIR3_MULTI_SERVER=true`. Public URLs use `/{server}/{bucket}/{key}`; the server alias is included in signed URL validation and ticket payloads. For connector-routed aliases, the Edge renders the NATS subject from `AIR3_NATS_SUBJECT_TEMPLATE` (default `air3.{server}`), and each connector sets `AIR3_SERVER_NAME` to subscribe to its derived subject.
43+
Multi-server mode is enabled with `AIR3_MULTI_SERVER=true`. Public URLs use `/{server}/{bucket}/{key}` unless an alias has an Edge-side default bucket from `S3_{SUFFIX}_BUCKET`. With a default such as `S3_BLUE_BUCKET=demo`, signed URLs may use `/blue/{key}`; the server alias and resolved bucket are still included in signed URL validation and ticket payloads. Short-form parsing wins for defaulted aliases, so every segment after `/{server}/` is treated as the key. For connector-routed aliases, the Edge renders the NATS subject from `AIR3_NATS_SUBJECT_TEMPLATE` (default `air3.{server}`), and each connector sets `AIR3_SERVER_NAME` to subscribe to its derived subject.
4444

4545
```mermaid
4646
sequenceDiagram
@@ -52,8 +52,8 @@ sequenceDiagram
5252
participant Green as Connector AIR3_SERVER_NAME=green
5353
participant S3 as Private S3
5454
55-
Client->>Edge: Signed GET /blue/demo/file.txt
56-
Edge->>Edge: Verify server=blue, bucket, key, method
55+
Client->>Edge: Signed GET /blue/file.txt (S3_BLUE_BUCKET=demo)
56+
Edge->>Edge: Verify server=blue, resolved bucket=demo, key, method
5757
Edge->>NATS: Publish ticket on air3.blue
5858
Blue->>NATS: Pull from air3.blue queue
5959
Green-->>NATS: Subscribed to air3.green only
@@ -64,7 +64,7 @@ sequenceDiagram
6464

6565
## Direct-server exception flow
6666

67-
Direct-server aliases are configured on the Edge with `AIR3_DIRECT_SERVERS` or `DIRECT_SERVERS` and per-alias `S3_{SUFFIX}_*` settings. They require multi-server paths to select the alias, but they bypass NATS and the Private Connector for that alias. This is useful only when you deliberately accept that the Edge has S3 credentials and network reachability for that storage. It is not the recommended private-storage boundary.
67+
Direct-server aliases are configured on the Edge with `AIR3_DIRECT_SERVERS` or `DIRECT_SERVERS` and per-alias `S3_{SUFFIX}_*` settings. They require multi-server paths to select the alias, but they bypass NATS and the Private Connector for that alias. A direct alias may also set `S3_{SUFFIX}_BUCKET` for short-form paths, and startup validates that default bucket against `S3_{SUFFIX}_ALLOWED_BUCKETS`. This is useful only when you deliberately accept that the Edge has S3 credentials and network reachability for that storage. It is not the recommended private-storage boundary.
6868

6969
```mermaid
7070
sequenceDiagram
@@ -75,8 +75,8 @@ sequenceDiagram
7575
participant NATS as NATS
7676
participant Connector as Private connector
7777
78-
Client->>Edge: Signed GET /alpha/demo/file.txt
79-
Edge->>Edge: Verify signature and alpha bucket allowlist
78+
Client->>Edge: Signed GET /alpha/file.txt (S3_ALPHA_BUCKET=demo)
79+
Edge->>Edge: Verify signature, resolved bucket, and alpha bucket allowlist
8080
Edge->>S3: Fetch object with S3_ALPHA_* credentials
8181
S3-->>Edge: Return object stream or metadata
8282
Edge-->>Client: Stream response
@@ -121,14 +121,14 @@ flowchart TB
121121
- **S3 credentials stay private by default:** In the recommended connector-routed topology, the Edge Gateway has absolutely zero knowledge of `AIR3_S3_ACCESS_KEY_ID` and `AIR3_S3_SECRET_ACCESS_KEY`. If the Edge is compromised, the attacker still cannot access your connector-only S3 buckets. Direct-server aliases intentionally opt out for their configured `S3_{SUFFIX}_*` credentials.
122122
- **Outbound-only application traffic:** The Private Connector has no open inbound ports. It only initiates connections *out* to NATS, S3, and the Edge's ingest endpoint.
123123
- **Separate public and ingest listeners:** Public requests hit the public listener. The private ingest listener requires strict mTLS authentication *and* a one-time ingest token generated by the Edge. The experimental HTTP/3, TCP, smux, and custom QUIC ingest transports use the same mTLS files, connector identity allowlist, and one-time token semantics as the default HTTP ingest path.
124-
- **Defense-in-depth allowlists:** Both the Edge and the Connector strictly validate allowed bucket names before taking any action. Direct-server aliases have their own Edge-enforced `S3_{SUFFIX}_ALLOWED_BUCKETS` allowlist.
124+
- **Defense-in-depth allowlists:** Both the Edge and the Connector strictly validate allowed bucket names before taking any action. Direct-server aliases have their own Edge-enforced `S3_{SUFFIX}_ALLOWED_BUCKETS` allowlist, and any `S3_{SUFFIX}_BUCKET` default for a direct alias must be included in that allowlist.
125125
- **Secret-safe logging:** Logs are designed to be safe to ship anywhere. They use request IDs and high-level outcomes—never logging HMAC secrets, full URLs, ingest tokens, or raw S3 credentials.
126126

127127
## Data Plane vs. Control Plane
128128

129129
| Plane | Path | Contains | Does not contain |
130130
| --- | --- | --- | --- |
131-
| Public data plane | Client to edge public listener | Public `GET`/`HEAD`, signed URL claims, response bytes | S3 credentials |
131+
| Public data plane | Client to edge public listener | Public `GET`/`HEAD`, signed URL claims, optional short-form default-bucket paths, response bytes | S3 credentials |
132132
| Control plane | Edge to NATS to connector | Request ID, method, optional server alias, bucket, key, range, deadline, HTTPS ingest URL/fallback, one-time ingest token | Object bytes / file data |
133133
| Private data plane | Connector to S3 | S3 object fetch and metadata requests | Public client connection |
134134
| Direct-server data plane | Edge to configured S3 endpoint | S3 requests using `S3_{SUFFIX}_*` credentials for opt-in direct aliases | NATS tickets / Private Connector path |
@@ -137,8 +137,9 @@ flowchart TB
137137
## Operational behavior
138138

139139
- NATS exclusively carries short-lived fetch tickets and control messages for connector-routed aliases. Queue-group semantics are unchanged: a ticket is delivered to one connector replica, and each connector uses a bounded local worker pool (`AIR3_CONNECTOR_WORKERS`) for concurrent ticket handling.
140-
- With `AIR3_MULTI_SERVER=true`, the Edge parses `/{server}/{bucket}/{key}` and publishes connector-routed tickets to `AIR3_NATS_SUBJECT_TEMPLATE` (default `air3.{server}`). Connectors set `AIR3_SERVER_NAME` to derive their subscription subject and reject mismatched tickets.
141-
- Direct-server aliases configured with `AIR3_DIRECT_SERVERS`/`DIRECT_SERVERS` bypass NATS and connector ingest for those aliases; the Edge enforces the direct alias bucket allowlist and fetches from S3 itself.
140+
- With `AIR3_MULTI_SERVER=true`, the Edge parses `/{server}/{bucket}/{key}` or, for aliases with `S3_{SUFFIX}_BUCKET`, short-form `/{server}/{key}` paths. It publishes connector-routed tickets to `AIR3_NATS_SUBJECT_TEMPLATE` (default `air3.{server}`). Connectors set `AIR3_SERVER_NAME` to derive their subscription subject and reject mismatched tickets.
141+
- Direct-server aliases configured with `AIR3_DIRECT_SERVERS`/`DIRECT_SERVERS` bypass NATS and connector ingest for those aliases; the Edge enforces the direct alias bucket allowlist, validates any direct default bucket against that allowlist, and fetches from S3 itself.
142+
- Signatures bind the resolved server, bucket, and key. Omitting a configured default bucket from the public URL does not make the bucket implicit in the signature or ticket; tampering with the server alias, key, or resolved bucket still fails validation.
142143
- `AIR3_INGEST_URL` remains the HTTPS ingest fallback/ticket URL in all modes and is used directly by the HTTP-family transports (`http`, `http1`, `http2`, `http3`) with the existing headers and body.
143144
- Custom stream transports (`tcp`, `smux`, `quic`) use the shared MessagePack metadata frame, raw object body, and ack semantics. `smux` multiplexes those streams over persistent mTLS TCP, with one smux stream per object; direct `quic` uses `AIR3_EDGE_INGEST_QUIC_ADDR`/`AIR3_INGEST_QUIC_ADDR`.
144145
- The Edge Gateway holds the pending client response until the Connector finishes fetching the object.

0 commit comments

Comments
 (0)