Skip to content

Follow-on to #18: install and run the test suite as a genuine non-superuser - #30

Draft
jnasbyupgrade wants to merge 2 commits into
Postgres-Extensions:fix/issue-14-cleanfrom
jnasbyupgrade:followon/nonsuperuser-test-coverage
Draft

Follow-on to #18: install and run the test suite as a genuine non-superuser#30
jnasbyupgrade wants to merge 2 commits into
Postgres-Extensions:fix/issue-14-cleanfrom
jnasbyupgrade:followon/nonsuperuser-test-coverage

Conversation

@jnasbyupgrade

Copy link
Copy Markdown
Collaborator

Stacked on #18 -- based on that PR's branch (fix/issue-14-clean, now also pushed here to Postgres-Extensions/test_factory so this PR can be opened against the real repo instead of fork-to-fork). GitHub will retarget this to master automatically once #18 merges.

Background

While updating #18, the question came up: could we run more of this test suite as a non-superuser, given #18 was exactly the kind of bug (SET ROLE permission check) that a superuser-run test suite can't naturally catch? That turned into two rounds:

Round 1 (running the application-level tests as non-superuser): found that test/helpers/create.sql already switches into a non-superuser test_role for most of base.sql/pgtap.sql -- but via SET ROLE, not SET SESSION AUTHORIZATION. That distinction matters: SET ROLE only changes current_user; Postgres's own permission check for a further SET ROLE (the exact class of check #18's bug was in) is based on session_user, which SET ROLE leaves untouched. Under pg_regress's superuser connection, that silently bypassed this whole class of check for the rest of the file. Fixed by switching to SET SESSION AUTHORIZATION. Also added test/sql/security.sql, proving the public tf.* API needs nothing beyond a freshly-created, unprivileged role.

Round 2 (actually installing the extension as non-superuser, closing the real gap #18's own description calls out: "can't be reproduced under pg_regress, which runs as a superuser"):

  • Marked both control files superuser = false -- the pre-PG13 mechanism (not trusted, which is an unrecognized control-file key on PG10-12 and errors out entirely there, not just for non-superuser attempts; this project's CI still tests those versions). Verified empirically.
  • test/sql/install.sql now creates a disposable NOSUPERUSER + CREATEROLE role (mirroring what a real RDS/Aurora master user has) and installs through it via SET SESSION AUTHORIZATION, replacing the indirect pg_auth_members proxy check from Fix CREATE EXTENSION on PG16+ for non-superuser installs (#14) #18 with a genuine end-to-end repro: before Fix CREATE EXTENSION on PG16+ for non-superuser installs (#14) #18's fix this fails with "must be able to SET ROLE test_factory__owner"; after the fix it succeeds.
  • Needed two grants beyond CREATEROLE, found by actually running this rather than reasoning about it: USAGE on the tap schema (pgtap test-harness necessity) and CREATE on the current database (never granted to PUBLIC by default -- only CONNECT/TEMP are).
  • Fixed a real local-iteration flakiness this surfaced: test_factory__owner is deliberately left behind by DROP EXTENSION (so a real install/uninstall cycle by the same installer keeps working), but this test creates a fresh disposable installer role every run, so an orphaned owner role from a previous run belongs to an installer that no longer exists, breaking GRANT ... WITH SET. install.sql now drops both roles at start and end of the file.

Test plan

  • make test -- all 4 regression tests pass (base, install, pgtap, security)
  • Verified stable across many repeated make test runs against the same live cluster (this surfaced and fixed the role-leakage flakiness above)
  • test/expected/base.out/pgtap.out/security.out unchanged; install.out updated only for the renamed test description

🤖 Generated with Claude Code

pg_regress always connects as a superuser, so CREATE EXTENSION itself
can't be exercised as non-superuser here (test_factory isn't marked
trusted, and making it so is a real production behavior change, not a
test-harness concern). But the application-level testing that runs
after install doesn't need to stay superuser, and testing it that way
misses exactly the class of bug issue Postgres-Extensions#14 was.

Two changes:

- test/helpers/create.sql now uses SET SESSION AUTHORIZATION instead of
  SET ROLE to switch into test_role. SET ROLE only changes current_user;
  a further SET ROLE's own permission check (like the one test_factory's
  install performs, and like issue Postgres-Extensions#14's bug) is based on session_user,
  which SET ROLE leaves untouched. Under pg_regress's superuser
  connection, that means SET ROLE alone silently leaves this whole class
  of check bypassed for the rest of the file -- SET SESSION AUTHORIZATION
  actually drops it.

- New test/sql/security.sql proves the public tf.* API needs nothing
  beyond what a freshly-created, unprivileged role gets by default (no
  owned schema, no explicit grants, not a member of test_factory__owner):
  register/get work end to end, and the role still can't SET ROLE into
  test_factory__owner.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
…ions#18 test gap

Mark both control files `superuser = false` (the pre-PG13 mechanism,
not `trusted` -- `trusted` is an unrecognized control-file key on PG10-12,
which this project's CI still tests, and errors out entirely there, not
just for non-superuser attempts). Verified empirically against this
container's PG12 and PG17 clusters.

test/sql/install.sql now creates a disposable NOSUPERUSER + CREATEROLE
role (mirroring what a real RDS/Aurora master user has) and installs
through it via SET SESSION AUTHORIZATION, replacing the indirect
pg_auth_members proxy check from Postgres-Extensions#18 with a genuine end-to-end repro:
before the Postgres-Extensions#18 fix this fails with "must be able to SET ROLE
test_factory__owner"; after the fix it succeeds.

Two extra grants were needed beyond CREATEROLE, found by actually running
this rather than reasoning about it: USAGE on the tap schema (a pgtap
test-harness necessity, unrelated to what's under test) and CREATE on the
current database (never granted to PUBLIC by default -- only CONNECT/TEMP
are -- unlike what I'd assumed).

Also fixed a real local-iteration flakiness this surfaced: test_factory__owner
is deliberately left behind by DROP EXTENSION so a real install/uninstall
cycle by the same installer keeps working, but this test creates a fresh
disposable installer role every run, so an orphaned owner role from a
previous run of this file belongs to an installer that no longer exists,
breaking the GRANT ... WITH SET. install.sql now drops both roles at
start and end. Verified stable across many repeated `make test` runs
against the same cluster.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5dfa1fbc-fad5-4960-8fd8-2f53aaf8cc7d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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