Skip to content

feat(provision): prod data plane is internal-only by default#26

Merged
valvesss merged 1 commit into
mainfrom
hotfix/prod-internal-data-plane
Jul 6, 2026
Merged

feat(provision): prod data plane is internal-only by default#26
valvesss merged 1 commit into
mainfrom
hotfix/prod-internal-data-plane

Conversation

@valvesss

@valvesss valvesss commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What

Makes a prod project's auth/rest sidecars internal-only — no public edge route — unless the project explicitly opts into a public data plane.

Why

A prod project's auth/REST are server-side infra: the app reaches them over the internal network alias (hauldr-auth-<name> / hauldr-rest-<name>). Publishing a public route only widens the attack surface — RLS becomes the sole guard and the PostgREST OpenAPI enumerates the schema to anonymous callers. This is why the fleet's public data-plane kept regressing after every manual hardening: the provisioner re-attached the public fqdn on each re-provision.

How

  • routeDomainsFor() returns [] (no route) for prod auth/rest unless public_data_plane is set (migration 0015).
  • Escape hatch for a client-side SPA (browser talks to the data plane with supabase-js): set public_data_plane = true → routed publicly, guarded by RLS + an edge WAF.
  • Dev stays public (debugging a throwaway env). Storage and realtime unchanged — storage needs a stable internal alias before it can go internal (follow-up); realtime routes on its own host that browsers require.
  • The reconcile sweep (POST /v1/reconcile) applies the policy to existing sidecars — PATCH domains + redeploy the drifted ones.
  • Adds config.test.ts (the control plane's first test): prod internal / dev public / SPA opt-in / storage-realtime-unaffected.

Rollout (ordered — reconcile is manual, no auto-run, so no race)

  1. Deploy this (bootstrap applies migration 0015).
  2. update projects set public_data_plane = true where name = 'viken'; (the one client-side SPA).
  3. POST /v1/reconcile → closes prod auth/rest fleet-wide; viken stays public.
  4. Verify tenants: curl <t>.hauldr.<zone>/rest/v1/ → 404 for server-side apps, 200 for viken.

Doctrine: Edda ADR 0013.

🤖 Generated with Claude Code

A prod project's auth/REST sidecars are server-side infrastructure — the app
reaches them over the internal network alias — so publishing a public edge route
only widens the attack surface: RLS becomes the sole guard and the PostgREST
OpenAPI enumerates the schema to anonymous callers. This is how the fleet's
public data-plane kept regressing after each hardening: the provisioner
re-attached the public fqdn on every re-provision.

routeDomainsFor() now returns NO domains for prod auth/rest unless the project
opts into a public data plane (public_data_plane, migration 0015) — the escape
hatch for a client-side SPA that genuinely needs the browser to reach the data
plane (guarded by RLS + an edge WAF). Dev stays public; storage and realtime are
unchanged (storage needs a stable internal alias first; realtime routes on its
own host). The reconcile sweep applies the policy to existing sidecars.

Adds a config.test.ts covering the policy (first test in the control plane).

Doctrine: Edda ADR 0013.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@valvesss valvesss merged commit ece5763 into main Jul 6, 2026
3 checks passed
@valvesss valvesss deleted the hotfix/prod-internal-data-plane branch July 6, 2026 01:30
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