mirror of
https://github.com/postgres/postgres.git
synced 2025-06-22 02:52:08 +03:00
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 commit151c22deee
due to concerns about search_path tricks that could be used to escalate privileges to the table owner. Commits2af07e2f74
,59825d1639
, andc7ea3f4229
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
This commit is contained in:
@ -28,6 +28,7 @@
|
||||
#include "catalog/indexing.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/pg_am.h"
|
||||
#include "catalog/pg_authid.h"
|
||||
#include "catalog/pg_constraint.h"
|
||||
#include "catalog/pg_database.h"
|
||||
#include "catalog/pg_inherits.h"
|
||||
@ -2947,6 +2948,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
|
||||
char relkind;
|
||||
struct ReindexIndexCallbackState *state = arg;
|
||||
LOCKMODE table_lockmode;
|
||||
Oid table_oid;
|
||||
|
||||
/*
|
||||
* Lock level here should match table lock in reindex_index() for
|
||||
@ -2986,14 +2988,19 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
|
||||
errmsg("\"%s\" is not an index", relation->relname)));
|
||||
|
||||
/* Check permissions */
|
||||
if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX, relation->relname);
|
||||
table_oid = IndexGetRelation(relId, true);
|
||||
if (OidIsValid(table_oid))
|
||||
{
|
||||
AclResult aclresult;
|
||||
|
||||
aclresult = pg_class_aclcheck(table_oid, GetUserId(), ACL_MAINTAIN);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, OBJECT_INDEX, relation->relname);
|
||||
}
|
||||
|
||||
/* Lock heap before index to avoid deadlock. */
|
||||
if (relId != oldRelId)
|
||||
{
|
||||
Oid table_oid = IndexGetRelation(relId, true);
|
||||
|
||||
/*
|
||||
* If the OID isn't valid, it means the index was concurrently
|
||||
* dropped, which is not a problem for us; just return normally.
|
||||
@ -3029,7 +3036,7 @@ ReindexTable(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLev
|
||||
(params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
|
||||
ShareUpdateExclusiveLock : ShareLock,
|
||||
0,
|
||||
RangeVarCallbackOwnsTable, NULL);
|
||||
RangeVarCallbackMaintainsTable, NULL);
|
||||
|
||||
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
|
||||
ReindexPartitions(stmt, heapOid, params, isTopLevel);
|
||||
@ -3113,7 +3120,8 @@ ReindexMultipleTables(const ReindexStmt *stmt, const ReindexParams *params)
|
||||
{
|
||||
objectOid = get_namespace_oid(objectName, false);
|
||||
|
||||
if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()))
|
||||
if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()) &&
|
||||
!has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
|
||||
objectName);
|
||||
}
|
||||
@ -3125,7 +3133,8 @@ ReindexMultipleTables(const ReindexStmt *stmt, const ReindexParams *params)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("can only reindex the currently open database")));
|
||||
if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()))
|
||||
if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()) &&
|
||||
!has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
|
||||
get_database_name(objectOid));
|
||||
}
|
||||
@ -3197,15 +3206,12 @@ ReindexMultipleTables(const ReindexStmt *stmt, const ReindexParams *params)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* The table can be reindexed if the user is superuser, the table
|
||||
* owner, or the database/schema owner (but in the latter case, only
|
||||
* if it's not a shared relation). object_ownercheck includes the
|
||||
* superuser case, and depending on objectKind we already know that
|
||||
* the user has permission to run REINDEX on this database or schema
|
||||
* per the permission checks at the beginning of this routine.
|
||||
* We already checked privileges on the database or schema, but we
|
||||
* further restrict reindexing shared catalogs to roles with the
|
||||
* MAINTAIN privilege on the relation.
|
||||
*/
|
||||
if (classtuple->relisshared &&
|
||||
!object_ownercheck(RelationRelationId, relid, GetUserId()))
|
||||
pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK)
|
||||
continue;
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user