From ecb0fd33720fab91df1207e85704f382f55e1eb7 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 13 Mar 2024 14:49:26 -0500 Subject: Reintroduce MAINTAIN privilege and pg_maintain predefined role. Roles with MAINTAIN on a relation may run VACUUM, ANALYZE, REINDEX, REFRESH MATERIALIZE VIEW, CLUSTER, and LOCK TABLE on the relation. Roles with privileges of pg_maintain may run those same commands on all relations. This was previously committed for v16, but it was reverted in commit 151c22deee due to concerns about search_path tricks that could be used to escalate privileges to the table owner. Commits 2af07e2f74, 59825d1639, and c7ea3f4229 resolved these concerns by restricting search_path when running maintenance commands. Bumps catversion. Reviewed-by: Jeff Davis Discussion: https://postgr.es/m/20240305161235.GA3478007%40nathanxps13 --- src/backend/commands/cluster.c | 43 +++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) (limited to 'src/backend/commands/cluster.c') diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index cea7c8ea368..c04886c4090 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -77,6 +77,7 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, static List *get_tables_to_cluster(MemoryContext cluster_context); static List *get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid); +static bool cluster_is_permitted_for_relation(Oid relid, Oid userid); /*--------------------------------------------------------------------------- @@ -144,7 +145,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel) tableOid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, 0, - RangeVarCallbackOwnsTable, NULL); + RangeVarCallbackMaintainsTable, + NULL); rel = table_open(tableOid, NoLock); /* @@ -362,8 +364,8 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params) */ if (recheck) { - /* Check that the user still owns the relation */ - if (!object_ownercheck(RelationRelationId, tableOid, save_userid)) + /* Check that the user still has privileges for the relation */ + if (!cluster_is_permitted_for_relation(tableOid, save_userid)) { relation_close(OldHeap, AccessExclusiveLock); goto out; @@ -1619,7 +1621,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, /* - * Get a list of tables that the current user owns and + * Get a list of tables that the current user has privileges on and * have indisclustered set. Return the list in a List * of RelToCluster * (stored in the specified memory context), each one giving the tableOid * and the indexOid on which the table is already clustered. @@ -1636,8 +1638,8 @@ get_tables_to_cluster(MemoryContext cluster_context) List *rtcs = NIL; /* - * Get all indexes that have indisclustered set and are owned by - * appropriate user. + * Get all indexes that have indisclustered set and that the current user + * has the appropriate privileges for. */ indRelation = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -1651,7 +1653,7 @@ get_tables_to_cluster(MemoryContext cluster_context) index = (Form_pg_index) GETSTRUCT(indexTuple); - if (!object_ownercheck(RelationRelationId, index->indrelid, GetUserId())) + if (!cluster_is_permitted_for_relation(index->indrelid, GetUserId())) continue; /* Use a permanent memory context for the result list */ @@ -1699,10 +1701,13 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid) if (get_rel_relkind(indexrelid) != RELKIND_INDEX) continue; - /* Silently skip partitions which the user has no access to. */ - if (!object_ownercheck(RelationRelationId, relid, GetUserId()) && - (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) || - IsSharedRelation(relid))) + /* + * It's possible that the user does not have privileges to CLUSTER the + * leaf partition despite having such privileges on the partitioned + * table. We skip any partitions which the user is not permitted to + * CLUSTER. + */ + if (!cluster_is_permitted_for_relation(relid, GetUserId())) continue; /* Use a permanent memory context for the result list */ @@ -1718,3 +1723,19 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid) return rtcs; } + +/* + * Return whether userid has privileges to CLUSTER relid. If not, this + * function emits a WARNING. + */ +static bool +cluster_is_permitted_for_relation(Oid relid, Oid userid) +{ + if (pg_class_aclcheck(relid, userid, ACL_MAINTAIN) == ACLCHECK_OK) + return true; + + ereport(WARNING, + (errmsg("permission denied to cluster \"%s\", skipping it", + get_rel_name(relid)))); + return false; +} -- cgit v1.2.3