Skip to content

Propagate GRANTED BY when deparsing table grants - #8767

Open
ibrahim halatci (ihalatci) wants to merge 1 commit into
pg19-supportfrom
ihalatci-fix-table-granted-by-grantor
Open

Propagate GRANTED BY when deparsing table grants#8767
ibrahim halatci (ihalatci) wants to merge 1 commit into
pg19-supportfrom
ihalatci-fix-table-granted-by-grantor

Conversation

@ihalatci

Copy link
Copy Markdown
Contributor

Summary

Preserve a table GRANT/REVOKE statement's explicit GRANTED BY role when Citus deparses and propagates the command to physical shards on PostgreSQL 19.

This PR is intentionally limited to table-grantor serialization. It is separate from #8766 and does not address that issue's scope.

Tracks #8759

Umbrella tracking: #8597

Problem and impact

PostgreSQL 19 allows an acting role to name an eligible inherited role as the grantor. Citus previously omitted that grantor while rebuilding the worker command, allowing each shard to select a different best grantor from the coordinator.

The regression reproduces this with a third-party table owner and two eligible grantors inherited by the acting role. Without the fix, coordinator and physical-shard ACLs diverge. A selective revoke can then leave the coordinator reporting the privilege while a shard rejects access, or leave shard privileges after the coordinator-side privilege is revoked.

Coverage

  • third-party owner and two eligible inherited grantors
  • coordinator-versus-physical-shard ACL grantor parity
  • selective revoke behavior and has_table_privilege() parity
  • a quoted grantor role name
  • WITH GRANT OPTION and matching REVOKE GRANT OPTION FOR
  • a negative control without explicit GRANTED BY

PG19 validation

  • git diff --check origin/pg19-support...HEAD
  • targeted citus_indent --quiet --check for src/backend/distributed/commands/grant.c
  • clean PG19beta2 build and install
  • focused src/test/regress/citus_tests/run_test.py pg19 (all 7 scheduled tests passed)

PreprocessGrantStmt() builds the DDL it sends to the workers by hand and
never emitted GrantStmt.grantor, so the GRANTED BY clause was silently
dropped on the way to the shards.

This was unreachable before PostgreSQL 19: until then GRANTED BY only
accepted the current user, so the grantor the coordinator recorded was
always the role we connect to the workers as, and each worker
independently arrived at the same answer. PostgreSQL 19 (commit dd1398f1)
relaxed the clause to accept any role whose privileges the current user
inherits. Once the named grantor can differ from the connecting role, the
workers no longer have enough information to reproduce the coordinator's
ACL: they run select_best_grantor() on their own and may legitimately pick
a different eligible role.

The result is a coordinator/shard ACL divergence that survives a revoke.
Granting the same privilege twice under two different grantors and then
revoking only one of them leaves the coordinator reporting
has_table_privilege() = true while the shards report false, so the
grantee's queries fail with a permission error even though the coordinator
says the privilege is held. The mirror construction also leaks privileges
on the shards after a coordinator-side revoke.

Emit GRANTED BY in the deparsed statement so the workers record the same
grantor the coordinator did. The clause goes after WITH GRANT OPTION for
GRANT, and after the grantee list but before CASCADE/RESTRICT for REVOKE.
GrantStmt.grantor has existed since PG14, so no version gate is needed;
the field is simply never set on PG16-18.

The regression test uses a third-party table owner and two eligible
grantors, both inherited by the acting role. The extra owner matters: if
the named grantor were also the role select_best_grantor() would have
chosen anyway, the test would pass even with the bug present. It asserts
the grantor recorded in the shard ACLs via aclexplode(), not just that
some privilege exists, and checks has_table_privilege() parity between the
coordinator and the shards after a selective revoke. A grantor whose name
requires quoting covers RoleSpecString(), and a grant carrying WITH GRANT
OPTION plus a matching REVOKE GRANT OPTION FOR exercise both slots the
grantor clause shares with the grant option.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.71%. Comparing base (db67cd7) to head (3257ed3).

Additional details and impacted files
@@               Coverage Diff                @@
##           pg19-support    #8767      +/-   ##
================================================
- Coverage         88.74%   88.71%   -0.04%     
================================================
  Files               289      289              
  Lines             65065    65068       +3     
  Branches           8200     8202       +2     
================================================
- Hits              57743    57726      -17     
- Misses             4957     4978      +21     
+ Partials           2365     2364       -1     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

2 participants