bazel l1 [fork 4/5]: multi-backend S3 map — fail-closed validation + per-request routing - #30
Conversation
840d6fb to
d066700
Compare
25e272c to
d2c394b
Compare
d066700 to
1a46c90
Compare
d2c394b to
8e73e82
Compare
8e73e82 to
f7b4f83
Compare
f7b4f83 to
026c58e
Compare
Adds the s3_proxy backends map: one minio client, transport, and upload queue per allowlisted backend, dispatched per request from the validated selection on the context, with the bucket applied per request (default bucket plus extra_buckets). Connection hygiene for LB-less MinIO endpoints: idle-conn caps (64 total / 32 per host by default) and periodic idle-connection recycling (conn_recycle_interval, default 5m), plus bounded upload timeouts, per-backend metrics labels, and lower per-backend upload-pool defaults in multi-backend mode. Config gains the backends map YAML surface with inheritance/merging and exactly-one-default validation; UploadReq captures the request-scoped selection at enqueue time. This commit also wires the S3-selector trust interceptors from the previous commit into main.go (their allowlist now exists) and refuses to start a backends-map node without both companion trust toggles. Co-authored-by: Cursor <cursoragent@cursor.com>
Two multi-backend sizing fixes from the L1 review rounds, landed ahead of the backends map being deployable: perBackendUploadLimits treated value-equality with the single-backend defaults as "unset", so an operator's explicit --num_uploaders 100 silently became 25 per backend in map mode. Presence is now recorded at both config sources (ctx.IsSet on the flag path, a pointer-field probe unmarshal on the YAML path) and consulted instead of values. The queue limits apply PER BACKEND and every queued upload holds an open reader FD, so the process-wide worst case is backends × max_queued_uploads. That aggregate is now asserted against half the soft RLIMIT_NOFILE at startup — a config that could exhaust NOFILE under write pressure fails the converge with the arithmetic spelled out, instead of killing the serving path later. Co-authored-by: Cursor <cursoragent@cursor.com>
assertAggregateUploadFDBudget runs inside config.Get and budgets against the soft NOFILE limit, but rlimit.Raise() only lifted it to the hard ceiling after parsing. A backend map that fits the post-raise limit could therefore fail startup against the unraised one. Raise first, then parse (bugbot, PR #30). Co-authored-by: Cursor <cursoragent@cursor.com>
026c58e to
f32a61a
Compare
f389172 to
0b2a6aa
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f32a61a. Configure here.
| merged.Endpoint = backend.Endpoint | ||
| if backend.DisableSSL != nil { | ||
| merged.DisableSSL = *backend.DisableSSL | ||
| } |
There was a problem hiding this comment.
SSL not inferred for override endpoints
Medium Severity
When a backends-map entry sets an explicit endpoint and omits disable_ssl, SSL is inherited from the top-level config (usually enabled) instead of from the selector key’s http/https scheme. The unset-endpoint path does derive disable_ssl from that scheme. Adding a private dial endpoint to an http:// selector can therefore flip the backend to TLS without any SSL-related config change, and MinIO calls fail at handshake time.
Reviewed by Cursor Bugbot for commit f32a61a. Configure here.


Piece 4 of 5 peeled off
shreyas/bazel-l1-goal-state(design record: FastActions/fa#4779). Base: piece 3. Two commits (b4845c1,25e272c).Summary
s3proxy.NewMulti, configs3_proxy.backends): the L1 validates the forwarded(endpoint, bucket)selector against an operator allowlist — fail-closed, opaque string match, no URL normalization — and routes the operation to that backend, including queued async write-throughs (the selector rides each queued item). Unknown selector → typedS3_BACKEND_SELECTOR_REJECTEDmarker → client degrades to a metered miss (piece 3).num_uploaders/max_queued_uploadsequal to the single-backend default is no longer silently lowered per map entry (presence tracked for both CLI flags and YAML keys).max_queued_uploadsexceeds half of softRLIMIT_NOFILE(every queued upload holds an open reader FD) — a config that could exhaust FDs fails the converge, not the serving path.Dark until a node is deployed with a non-empty backends map; merging is harmless.
Evidence
Staging E2E 2026-07-30: both nodes in three-entry map mode; two-bucket routing proven via per-backend outcome counters; unknown-selector and wrong-bucket rejections degraded to metered misses.
Made with Cursor
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled. (Staging)