Files
member-console/openspec/changes
cgalo5758 dd4a0e2ec0 Refuse to start on a runtime user that can get around the grants
Fifth section of #23: the start-up check. Before any migration runs, on
both connections, the console refuses when the two strings log in as one
user or reach different databases, when the runtime user is a superuser
or a member of a superuser role, when it owns or is a member of the
owner of the database, when the database is owned by neither the
migration user nor a superuser, when the runtime user can create objects
in the database, in public or in a console schema, or when it owns an
object in those schemas that a name lookup could resolve (relations,
functions, operators, types, and operator classes, families and
extensions database-wide). After the migrations and the boot grant, on
the runtime connection, it refuses when console_runtime is missing, when
the runtime user does not inherit it or holds it with SET, when the
schema is behind the code, when the runtime user reaches an owner role,
owns an object, holds any direct membership but console_runtime, or
holds a privilege an enclosure withholds. Each refusal names the
statement that fixes it, with names quoted by the database; a check
query that fails refuses too. migrate up, down and status refuse a
database whose owner is neither user before goose runs.

The boot grant is now WITH INHERIT TRUE, SET FALSE: the runtime user
needs only to inherit console_runtime, and without SET on that first
membership no SET ROLE reaches a role through it. Granting again
updates an existing membership in place; this worktree's runtime user
held SET from the previous section and lost it at the suite's boot.

Every check runs in a read-only REPEATABLE READ transaction that begins
with SET LOCAL search_path = pg_catalog, pg_temp, and names every
function and catalog relation with pg_catalog., so nothing the runtime
user planted (a function such as public.quote_ident(name), or an
operator such as public.=(oid, integer), which qualified function names
alone did not exclude) runs in the migration user's session or writes a
statement the console prints. TestCheckQueriesQualifyEveryName scans
the query constants and fails on an unqualified function call or catalog
relation.

Full suite on reset databases: 4370 pass, 15 skip, none failed. The
console booted on the test stack and logged "runtime database user
checked" with the runtime and migration users. make lint passes. Break
runs that disable an early refusal or a test's cleanup can leave a role
or a membership in the shared test cluster; those were removed after
the run.

Deliberate breaks, scripts/mutation-check -side new, all CAUGHT (76):
  checks 3a to 3c run before checks 1 to 3
  checks 1 to 4 run after the migrations
  the boot grant runs before the superuser check
  the database-ownership check removed (TestCheckBeforeMigrating)
  the database-ownership check removed (TestOpenRuntimeDatabase)
  check 4 tests datdba = current_user in place of MEMBER
  check 4 reads the first database in pg_database
  check 4 without its datname = current_database() filter
  the migration identity read outside the pinned read-only transaction, with an unqualified quote_ident
  the runtime identity read outside the pinned read-only transaction, with an unqualified quote_ident
  the migration pool opened without role=none (owner set its own role)
  check 12 reads has_schema_privilege in place of the ACL entries
  check 12's schema CREATE query removed (TestCheckBeforeMigrating)
  check 12's schema CREATE query removed (TestOpenRuntimeDatabase)
  check 12's ownership query removed (TestCheckBeforeMigrating)
  check 12's ownership query removed (TestOpenRuntimeDatabase)
  check 12 runs after the migrations
  the boot grant's pg_catalog.format written format
  check 12's queries read outside the pinned read-only transaction (TestCheckBeforeMigrating)
  check 12's queries read outside the pinned read-only transaction (TestOpenRuntimeDatabase)
  CheckRuntimeUser's transaction without the search-path pin
  the second query of checks 9 and 12 casts the login with regrole
  CheckMigrationDatabaseOwner removed from dbMigration
  dbMigration checks before up only
  CheckMigrationDatabaseOwner removed from dbRuntime
  the owner half refuses a superuser-owned database
  copy 3d prints the database name unquoted
  check 11 without the four-privilege read
  copy 9 prints the user in place of the grantee
  check 11's grantee filter dropped
  check 11's predefined-role query removed
  copy 3c prints the direct role unquoted
  copy 6 prints the direct role unquoted
  check 6 tests MEMBER in place of USAGE
  check 8 tests USAGE in place of MEMBER
  check 9 without functions
  check 9 without public
  check 9 tests owner = current_user in place of MEMBER
  the direct-membership check removed (TestCheckRuntimeUser)
  the direct-membership check removed (TestOpenRuntimeDatabase)
  the direct-membership check counts console_runtime
  the direct-membership check runs before check 8
  check 11 removed
  check 3 without its membership half
  copy 5 quotes the name in Go (%q)
  an exemption when env is development
  a failed check query passes
  the scratch database is dropped after the login that owns it (test-side)
  an unqualified quote_ident( in the identity query
  an unqualified pg_roles in the owner check
  PendingMigrations calls to_regclass without pg_catalog.
  the query constants renamed so none begins query (test-side)
  the search-path pin removed from the pinned read-only transaction
  the search-path pin removed from the savepoint a test runs a check in
  check 5 removed
  check 6 does not read the SET option
  check 6 does not read the SET option (separate-step start)
  the boot grant leaves the SET option (TestGrantRuntimeRole)
  the boot grant leaves the SET option (TestOpenRuntimeDatabase)
  the database CREATE query removed (TestCheckBeforeMigrating)
  the database CREATE query removed (TestOpenRuntimeDatabase)
  the database CREATE query reads has_database_privilege in place of the ACL entries
  check 12's ownership query reads public only
  check 12's ownership query reads public only (TestOpenRuntimeDatabase)
  check 9 without operators
  check 9 without types
  check 9 without operator classes
  check 9 without operator families
  check 9 without extensions
  start's refusal log lacks the owner refusal
  the scan folds no case (test-side)
  the scan reads a quoted call as no call (test-side)
  the scan reads no line comment (test-side)
  the scan skips a concatenated constant (test-side)
  the scan passes a query constant without a value (test-side)
  copy 5a prints only the GRANT, not the REVOKE by the grantor
2026-10-23 09:43:35 -04:00
..