Skip to content

Buffer proxied request bodies; harden cloudsql-proxy readiness#365

Open
Cre-eD wants to merge 2 commits into
mainfrom
fix/csql-probe-caddy-request-buffers
Open

Buffer proxied request bodies; harden cloudsql-proxy readiness#365
Cre-eD wants to merge 2 commits into
mainfrom
fix/csql-probe-caddy-request-buffers

Conversation

@Cre-eD

@Cre-eD Cre-eD commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Context

Two production-observed failure modes on GKE Autopilot deployments. Both fixes are systemic: any SC consumer running WSGI apps behind the generated Caddy, or cloudsql-proxy native sidecars, is exposed.

1. request_buffers on generated reverse_proxy blocks

Chunked request bodies (no Content-Length) pass through Caddy to upstreams as-is. WSGI frameworks read the body by CONTENT_LENGTH — Django (#28668) gets an empty request.body. Observed blast radius: webhook handlers returning 500 (sender retry storms), HMAC signature verification failing over the empty body, «CSRF token missing» 403s.

Fix: every generated reverse_proxy block gets request_buffers (default 1MiB; bodies above the buffer keep streaming as before). Per-stack knob lbConfig.requestBufferSize:

  • parsed as a byte size (humanize), capped at 16MiB, re-rendered as a plain byte count — no user-controlled text ever reaches the shared multi-tenant Caddyfile (a malformed value would otherwise produce a parse-fatal aggregated config detonating at the next Caddy pod restart);
  • invalid values fail the offending stack's deploy with a clear error;
  • "0" omits the directive entirely — the escape hatch for edges pinned to Caddy < 2.6.0;
  • consumers that shipped an interim extraHelpers: [request_buffers ...] workaround get a render-time warning: Caddy's last-wins would silently override the typed knob — drop the workaround on upgrade.

2. cloudsql-proxy sidecar probes

  • readiness AND liveness timeoutSeconds 3s → 10s (both served by the same starvable health server; a 3s liveness timeout would restart the sidecar — severing live DB connections — under the exact pressure the readiness change tolerates); startup stays tight deliberately (documented).
  • CPU request 50m → 100m.
  • fixed a wrong comment claiming native-sidecar readiness doesn't gate pod readiness (it does, KEP-753).

Review

Multi-model panel (4 independent reviewer lenses + 2 external models) on the first revision; all confirmed findings addressed in the second commit: input validation/normalization, liveness parity, JSON schema regeneration, prefix-template test coverage (mutation-verified), line-anchored assertions, all probe numerics pinned.

Tests

go build, go vet, gofmt clean; kubernetes/gcp/api suites green (-count=1). New: RequestBuffers Default / DefaultWithLbConfigSet / OnPrefixTemplate / OverrideNormalized / ZeroOmitsDirective / InvalidValueFailsDeploy / CapEnforced; ProbesTolerantToStarvation pins every probe numeric.

Follow-ups (out of scope here)

  • timeouts { read_body } default for the edge servers block (slow-body memory-pinning DoS surface; needs its own compat analysis).
  • cloud-sql-proxy image bump 2.8.1 → current (file touched here, but runtime behavior change deserves its own PR).
  • Digest pinning for simplecontainer/caddy:latest / simplecontainer/kubectl:latest floating tags.
  • caddy.Dockerfile: xcaddy builds v2.11.3 while both FROMs pin 2.11.4 — sync.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Semgrep Scan Results

Repository: api | Commit: 9e54a4f

Check Status Details
⚠️ Semgrep Warning 1 warning(s), 5 total

Scanned at 2026-07-18 04:56 UTC

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Security Scan Results

Repository: api | Commit: 9e54a4f

Check Status Details
✅ Secret Scan Pass No secrets detected
⚠️ Dependencies (Trivy) High 2 high, 5 total
⚠️ Dependencies (Grype) High 2 high, 3 total
📦 SBOM Generated 523 components (CycloneDX)

Scanned at 2026-07-18 04:56 UTC

@github-actions

Copy link
Copy Markdown

📊 Statement coverage

Measured on the documented included set (see docs/TESTING.md → Coverage scope). Observe-only — no regression gate is enforced yet.

Scope This PR main baseline Δ
Included set (Gold-tier denominator) 90.3% 90.3% +0.0 pp
Full set (whole repo, transparency) 28.0% 28.0% +0.0 pp

Baseline: main @ 60b1958

@blacksmith-sh

This comment has been minimized.

reverse_proxy render: add request_buffers (default 1MiB, per-stack
lbConfig.requestBufferSize, "0" disables). Chunked request bodies
reach WSGI upstreams without Content-Length; Django (ticket #28668)
reads an empty body on such requests - webhook 500s, HMAC signature
failures over the empty body, lost CSRF tokens. Bodies above the
buffer keep streaming as before.

cloudsql-proxy sidecar: readiness timeout 3s->10s, CPU request
50m->100m. At 50m the health server starves under node CPU pressure;
3s probe timeouts flap pods out of Service endpoints (sidecar
readiness gates pod readiness per KEP-753 - the old comment claimed
otherwise and is fixed too). Combined with node consolidation this
can briefly leave a Service with zero ready pods.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
@Cre-eD
Cre-eD force-pushed the fix/csql-probe-caddy-request-buffers branch from 1b9ffc1 to 0c72dd4 Compare July 18, 2026 04:36
…egen

- lbConfig.requestBufferSize is now parsed (humanize), capped at 16MiB,
  and re-rendered as a plain byte count - no user-controlled text
  reaches the shared Caddyfile (a malformed value previously produced a
  parse-fatal aggregated config detonating at the next Caddy restart,
  taking down every tenant). Invalid values fail only the offending
  stack's deploy. Field is now a plain string with omitempty (sibling
  idiom; keeps it out of the JSON schema's required list).
- "0" now OMITS the request_buffers directive entirely (placeholder
  carries the whole line) - real escape hatch for edges pinned to
  Caddy < 2.6.0. A render-time warning fires when extraHelpers carries
  a duplicate request_buffers (Caddy last-wins would silently override
  the typed knob).
- Liveness probe timeout 3s->10s to match readiness: /liveness is
  served by the same starvable health server; a 3s timeout would
  RESTART the sidecar (severing live DB connections) under the exact
  pressure the readiness change tolerates. Startup stays tight on
  purpose (documented).
- Regenerated docs/schemas (requestBufferSize in stackconfigcompose).
- Tests per panel findings: prefix-template coverage (mutation-verified
  to fail on regression), line-anchored+placement assertions instead of
  substrings, invalid/cap/zero/default-with-lbConfig cases, all probe
  numerics pinned. go-humanize promoted to direct dependency.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant