Skip to content

Commit 8b85a3d

Browse files
committed
fix: drop the rds_iam grant from 005 (it forces IAM-only auth on RDS)
Live prod verification caught this: granting rds_iam to a role makes IAM authentication MANDATORY on RDS (password auth fails with PAM authentication failed), and the Vercel runtime has no AWS credentials to mint IAM tokens. bench_read authenticates with a static password, so 005 now creates the role with no rds_iam grant (a future IAM switch re-adds it in a follow-up migration, atomically disabling the password). Prod state matches: the grant was revoked as master immediately after the 2026-06-10 bootstrap apply of 004+005, and SELECT-as-bench_read + INSERT-denied were verified live. 51 runner tests + 204 web tests green. Signed-off-by: "Connor Tsui" <connor@spiraldb.com>
1 parent 6523399 commit 8b85a3d

2 files changed

Lines changed: 13 additions & 16 deletions

File tree

migrations/005_read_role.sql

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,23 @@
1616
-- infrastructure (Vercel), so it gets an identity that cannot write even if
1717
-- its credential leaks.
1818
--
19-
-- Authentication: the role supports both auth paths `web/lib/db.ts`
20-
-- implements. For the static-password path the password is set OUT-OF-BAND by
21-
-- the operator (`ALTER ROLE bench_read PASSWORD '...'` as master; never in a
22-
-- committed migration). For a future IAM path the `rds_iam` grant below
23-
-- already applies on real RDS; the off-AWS guard mirrors 002/004.
19+
-- Authentication: STATIC PASSWORD, set OUT-OF-BAND by the operator
20+
-- (`ALTER ROLE bench_read PASSWORD '...'` as master; never in a committed
21+
-- migration). Deliberately NO `rds_iam` grant, unlike 002/004: on RDS,
22+
-- membership in `rds_iam` makes IAM authentication MANDATORY (password auth
23+
-- fails with "PAM authentication failed"), and the Vercel runtime has no AWS
24+
-- credentials to mint IAM tokens with. If the read path later moves to IAM
25+
-- (for example via Vercel-to-AWS OIDC federation), a follow-up migration
26+
-- grants `rds_iam` at switch time, which atomically disables the password.
2427
--
2528
-- Idempotent and substrate-portable, matching 002/004: `CREATE ROLE` is
26-
-- guarded (roles are cluster-global), the `rds_iam` grant is guarded behind an
27-
-- existence check so this also applies on vanilla Postgres (local dev + the
28-
-- testcontainer suites), and re-granting an existing privilege is a Postgres
29-
-- no-op.
29+
-- guarded (roles are cluster-global), and re-granting an existing privilege
30+
-- is a Postgres no-op.
3031
DO $$
3132
BEGIN
3233
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'bench_read') THEN
3334
CREATE ROLE bench_read WITH LOGIN;
3435
END IF;
35-
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'rds_iam') THEN
36-
GRANT rds_iam TO bench_read;
37-
END IF;
3836
END$$;
3937

4038
-- `bench_read` must reach objects in `public` but must NOT create any:

scripts/test_migrate_schema.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,10 +1204,9 @@ def test_bench_ingest_has_dml_only_on_data_tables(conn: psycopg.Connection) -> N
12041204

12051205

12061206
def test_real_migrations_create_bench_read_role(conn: psycopg.Connection) -> None:
1207-
"""`005_read_role.sql` creates a login-capable `bench_read` role. The
1208-
`rds_iam` grant is skipped on vanilla Postgres (the role does not exist
1209-
there); on real RDS it binds `bench_read` to IAM-token auth for a future
1210-
IAM read path."""
1207+
"""`005_read_role.sql` creates a login-capable `bench_read` role with NO
1208+
`rds_iam` grant: on RDS that membership makes IAM auth mandatory (password
1209+
auth fails), and the read service authenticates with a static password."""
12111210
runner.apply(conn, REPO_MIGRATIONS_DIR)
12121211

12131212
with conn.cursor() as cur:

0 commit comments

Comments
 (0)