diff --git a/src/backend/distributed/commands/distribute_object_ops.c b/src/backend/distributed/commands/distribute_object_ops.c index 8d1c6bc23..351ad22ec 100644 --- a/src/backend/distributed/commands/distribute_object_ops.c +++ b/src/backend/distributed/commands/distribute_object_ops.c @@ -810,6 +810,20 @@ static DistributeObjectOps Index_Drop = { .address = NULL, .markDistributed = false, }; + +#if PG_VERSION_NUM >= PG_VERSION_15 +static DistributeObjectOps Parameter_Grant = { + .deparse = DeparseGrantOnParameterStmt, + .qualify = NULL, + .preprocess = NULL, + .postprocess = PostprocessGrantParameterStmt, + .objectType = OBJECT_PARAMETER_ACL, + .operationType = DIST_OPS_ALTER, + .address = NULL, + .markDistributed = false, +}; +#endif /* PG_VERSION_NUM >= PG_VERSION_15 */ + static DistributeObjectOps Policy_Drop = { .deparse = NULL, .qualify = NULL, @@ -2117,6 +2131,13 @@ GetDistributeObjectOps(Node *node) return &Database_Grant; } +#if PG_VERSION_NUM >= PG_VERSION_15 + case OBJECT_PARAMETER_ACL: + { + return &Parameter_Grant; + } +#endif + default: { return &Any_Grant; diff --git a/src/backend/distributed/commands/parameter.c b/src/backend/distributed/commands/parameter.c new file mode 100644 index 000000000..ef961e1dd --- /dev/null +++ b/src/backend/distributed/commands/parameter.c @@ -0,0 +1,210 @@ +#include "postgres.h" + +#include "pg_version_constants.h" +#if PG_VERSION_NUM >= PG_VERSION_15 +#include "access/genam.h" +#include "catalog/namespace.h" +#include "catalog/pg_parameter_acl.h" +#include "commands/defrem.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/syscache.h" + +#include "distributed/commands.h" +#include "distributed/deparser.h" +#include "distributed/grant_utils.h" +#include "distributed/listutils.h" +#include "distributed/metadata_sync.h" + +static List * GenerateGrantOnParameterFromAclItem(char *parameterName, AclItem *aclItem); +static bool HasAclGrantOption(AclItem *aclItem, AclMode aclMode); +static void ValidatePermissionsAndGrants(AclItem *aclItem, AclMode modes[], int numModes); +static void CheckAndAppendGrantParameterQuery(List **queries, AclItem *aclItem, Oid + granteeOid, + char *parameterName, AclMode mode, + char *modeStr); +static void RemoveSemicolonFromEnd(char *query); + + +List * +PostprocessGrantParameterStmt(Node *node, const char *queryString) +{ + if (!ShouldPropagate()) + { + return NIL; + } + + EnsurePropagationToCoordinator(); + + char *command = DeparseTreeNode(node); + + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) command, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(REMOTE_NODES, commands); +} + + +/* + * GenerateGrantOnParameterFromAclItem generates the grant queries for the given aclItem. + * First it sets the current role to the grantor of the aclItem, then it appends the grant + * privilege queries for the aclItem, and finally it resets the role to the original role. + * Ex: If the aclItem has the grant option for ACL_SET, it generates the following queries: + * SET ROLE ; + * GRANT SET ON TO ; + * RESET ROLE; + */ +static List * +GenerateGrantOnParameterFromAclItem(char *parameterName, AclItem *aclItem) +{ + /* + * seems unlikely but we check if there is a grant option in the list without the actual permission + */ + ValidatePermissionsAndGrants(aclItem, (AclMode[]) { ACL_SET, ACL_ALTER_SYSTEM }, 2); + Oid granteeOid = aclItem->ai_grantee; + List *queries = NIL; + + queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor)); + + CheckAndAppendGrantParameterQuery(&queries, aclItem, granteeOid, parameterName, + ACL_SET, "SET"); + CheckAndAppendGrantParameterQuery(&queries, aclItem, granteeOid, parameterName, + ACL_ALTER_SYSTEM, + "ALTER SYSTEM"); + + queries = lappend(queries, "RESET ROLE"); + + return queries; +} + + +/* + * CheckAndAppendGrantParameterQuery checks if the aclItem has the given mode and if it has, it appends the + * corresponding query to the queries list. + * Ex: If the mode is ACL_SET, it appends the query "GRANT SET ON TO " + */ +static void +CheckAndAppendGrantParameterQuery(List **queries, AclItem *aclItem, Oid granteeOid, + char *parameterName, + AclMode mode, char *modeStr) +{ + AclResult aclresult = pg_parameter_aclcheck(parameterName, granteeOid, mode); + if (aclresult == ACLCHECK_OK) + { + char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRightsWithObjectName( + OBJECT_PARAMETER_ACL, granteeOid, parameterName, + modeStr, + HasAclGrantOption(aclItem, mode))); + + RemoveSemicolonFromEnd(query); + + *queries = lappend(*queries, query); + } +} + + +/* + * RemoveSemicolonFromEnd removes the semicolon at the end of the query if it exists. + */ +static void +RemoveSemicolonFromEnd(char *query) +{ + /* remove the semicolon at the end of the query since it is already */ + /* appended in metadata_sync phase */ + if (query[strlen(query) - 1] == ';') + { + query[strlen(query) - 1] = '\0'; + } +} + + +/* + * ValidatePermissionsAndGrants validates if the aclItem has the valid permissions and grants + * for the given modes. + */ +static void +ValidatePermissionsAndGrants(AclItem *aclItem, AclMode modes[], int numModes) +{ + AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_PARAMETER_ACL; + AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_PARAMETER_ACL; + + for (int i = 0; i < numModes; i++) + { + AclMode mode = modes[i]; + if ((grants & mode) && !(permissions & mode)) + { +#if PG_VERSION_NUM >= PG_VERSION_16 + ereport(ERROR, (errmsg("ACL item has no grant option for mode %lu", mode))); +#else + ereport(ERROR, (errmsg("ACL item has no grant option for mode %u", mode))); +#endif + } + } +} + + +/* + * HasAclGrantOption checks if the aclItem has the grant option for the given mode. + */ +static bool +HasAclGrantOption(AclItem *aclItem, AclMode aclMode) +{ + return (aclItem->ai_privs & ACL_GRANT_OPTION_FOR(aclMode)) != 0; +} + + +/* + * GenerateGrantStmtOnParametersFromCatalogTable generates the grant statements for the parameters + * from the pg_parameter_acl catalog table. + */ +List * +GenerateGrantStmtOnParametersFromCatalogTable(void) +{ + /* Open pg_shdescription catalog */ + Relation paramPermissionRelation = table_open(ParameterAclRelationId, + AccessShareLock); + + + int scanKeyCount = 0; + bool indexOk = false; + SysScanDesc scan = systable_beginscan(paramPermissionRelation, InvalidOid, + indexOk, NULL, scanKeyCount, NULL); + HeapTuple tuple; + List *commands = NIL; + while ((tuple = systable_getnext(scan)) != NULL) + { + bool isNull = false; + + TupleDesc tupdesc = RelationGetDescr(paramPermissionRelation); + + Datum aclDatum = heap_getattr(tuple, Anum_pg_parameter_acl_paracl, tupdesc, + &isNull); + Datum parameterNameDatum = heap_getattr(tuple, Anum_pg_parameter_acl_parname, + tupdesc, + &isNull); + + char *parameterName = TextDatumGetCString(parameterNameDatum); + + Acl *acl = DatumGetAclPCopy(aclDatum); + AclItem *aclDat = ACL_DAT(acl); + int aclNum = ACL_NUM(acl); + + + for (int i = 0; i < aclNum; i++) + { + commands = list_concat(commands, + GenerateGrantOnParameterFromAclItem( + parameterName, &aclDat[i])); + } + } + + /* End the scan and close the catalog */ + systable_endscan(scan); + table_close(paramPermissionRelation, AccessShareLock); + + return commands; +} + + +#endif /* PG_VERSION_NUM >= PG_VERSION_15 */ diff --git a/src/backend/distributed/deparser/deparse_parameter_stmts.c b/src/backend/distributed/deparser/deparse_parameter_stmts.c new file mode 100644 index 000000000..87a7fdb9e --- /dev/null +++ b/src/backend/distributed/deparser/deparse_parameter_stmts.c @@ -0,0 +1,68 @@ +/*------------------------------------------------------------------------- + * + * deparse_database_stmts.c + * All routines to deparse parameter statements. + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "pg_version_constants.h" +#if PG_VERSION_NUM >= PG_VERSION_15 + +#include "utils/builtins.h" + +#include "distributed/deparser.h" +#include "distributed/listutils.h" + +static void AppendGrantParameters(StringInfo buf, GrantStmt *stmt); +static void AppendGrantOnParameterStmt(StringInfo buf, GrantStmt *stmt); + +static void +AppendGrantParameters(StringInfo buf, GrantStmt *stmt) +{ + appendStringInfo(buf, " ON PARAMETER "); + + DefElem *def = NULL; + foreach_ptr(def, stmt->objects) + { + char *parameter = strVal(def); + appendStringInfoString(buf, quote_identifier(parameter)); + if (def != (DefElem *) lfirst(list_tail(stmt->objects))) + { + appendStringInfo(buf, ", "); + } + } +} + + +static void +AppendGrantOnParameterStmt(StringInfo buf, GrantStmt *stmt) +{ + Assert(stmt->objtype == OBJECT_PARAMETER_ACL); + + AppendGrantSharedPrefix(buf, stmt); + + AppendGrantParameters(buf, stmt); + + AppendGrantSharedSuffix(buf, stmt); +} + + +char * +DeparseGrantOnParameterStmt(Node *node) +{ + GrantStmt *stmt = castNode(GrantStmt, node); + Assert(stmt->objtype == OBJECT_PARAMETER_ACL); + + StringInfoData str = { 0 }; + initStringInfo(&str); + + AppendGrantOnParameterStmt(&str, stmt); + + return str.data; +} + + +#endif /* PG_VERSION_NUM >= PG_VERSION_15 */ diff --git a/src/backend/distributed/metadata/metadata_sync.c b/src/backend/distributed/metadata/metadata_sync.c index f73856169..b23b92dba 100644 --- a/src/backend/distributed/metadata/metadata_sync.c +++ b/src/backend/distributed/metadata/metadata_sync.c @@ -65,6 +65,7 @@ #include "distributed/coordinator_protocol.h" #include "distributed/deparser.h" #include "distributed/distribution_column.h" +#include "distributed/grant_utils.h" #include "distributed/listutils.h" #include "distributed/maintenanced.h" #include "distributed/metadata/dependency.h" @@ -115,11 +116,6 @@ static bool SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnE static void DropMetadataSnapshotOnNode(WorkerNode *workerNode); static char * CreateSequenceDependencyCommand(Oid relationId, Oid sequenceId, char *columnName); -static GrantStmt * GenerateGrantStmtForRights(ObjectType objectType, - Oid roleOid, - Oid objectId, - char *permission, - bool withGrantOption); static List * GetObjectsForGrantStmt(ObjectType objectType, Oid objectId); static AccessPriv * GetAccessPrivObjectForGrantStmt(char *permission); static List * GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid, @@ -130,7 +126,6 @@ static List * GenerateGrantOnFunctionQueriesFromAclItem(Oid schemaOid, static List * GrantOnSequenceDDLCommands(Oid sequenceOid); static List * GenerateGrantOnSequenceQueriesFromAclItem(Oid sequenceOid, AclItem *aclItem); -static char * GenerateSetRoleQuery(Oid roleOid); static void MetadataSyncSigTermHandler(SIGNAL_ARGS); static void MetadataSyncSigAlrmHandler(SIGNAL_ARGS); @@ -2257,18 +2252,66 @@ GenerateGrantOnDatabaseFromAclItem(Oid databaseOid, AclItem *aclItem) * The field `objects` of GrantStmt doesn't have a common structure for all types. * Make sure you have added your object type to GetObjectsForGrantStmt. */ -static GrantStmt * +GrantStmt * GenerateGrantStmtForRights(ObjectType objectType, Oid roleOid, Oid objectId, char *permission, bool withGrantOption) { + return BaseGenerateGrantStmtForRights(objectType, roleOid, objectId, NULL, permission, + withGrantOption); +} + + +/* + * GenerateGrantStmtForRightsWithObjectName is the function for creating + * GrantStmt's for all types of objects that are supported with object name. + * It takes parameters to fill a GrantStmt's fields and returns the GrantStmt. + * The field `objects` of GrantStmt doesn't have a common structure for all types. + * Make sure you have added your object type to GetObjectsForGrantStmt. + */ +GrantStmt * +GenerateGrantStmtForRightsWithObjectName(ObjectType objectType, + Oid roleOid, + char *objectName, + char *permission, + bool withGrantOption) +{ + return BaseGenerateGrantStmtForRights(objectType, roleOid, InvalidOid, objectName, + permission, withGrantOption); +} + + +/* + * BaseGenerateGrantStmtForRights is the base function for creating + * GrantStmt's for all types of objects that are supported with object . + * It is used by GenerateGrantStmtForRights and GenerateGrantStmtForRightsWithObjectName + * to support both object id and object name. + */ +GrantStmt * +BaseGenerateGrantStmtForRights(ObjectType objectType, + Oid roleOid, + Oid objectId, + char *objectName, + char *permission, + bool withGrantOption) +{ + /*either objectId or objectName should be valid */ + Assert(objectId != InvalidOid || objectName != NULL); + GrantStmt *stmt = makeNode(GrantStmt); stmt->is_grant = true; stmt->targtype = ACL_TARGET_OBJECT; stmt->objtype = objectType; - stmt->objects = GetObjectsForGrantStmt(objectType, objectId); + if (objectId != InvalidOid) + { + stmt->objects = GetObjectsForGrantStmt(objectType, objectId); + } + else + { + stmt->objects = list_make1(makeString(objectName)); + } stmt->privileges = list_make1(GetAccessPrivObjectForGrantStmt(permission)); stmt->grantees = list_make1(GetRoleSpecObjectForUser(roleOid)); stmt->grant_option = withGrantOption; @@ -2661,7 +2704,7 @@ SetLocalEnableMetadataSync(bool state) } -static char * +char * GenerateSetRoleQuery(Oid roleOid) { StringInfo buf = makeStringInfo(); @@ -4779,6 +4822,10 @@ PropagateNodeWideObjectsCommandList(void) List *alterRoleSetCommands = GenerateAlterRoleSetCommandForRole(InvalidOid); ddlCommands = list_concat(ddlCommands, alterRoleSetCommands); } +#if PG_VERSION_NUM >= PG_VERSION_15 + List *grantOnParameterCommands = GenerateGrantStmtOnParametersFromCatalogTable(); + ddlCommands = list_concat(ddlCommands, grantOnParameterCommands); +#endif /* PG_VERSION_NUM >= PG_VERSION_15 */ return ddlCommands; } diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index 19919e32c..998bd56b8 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -452,6 +452,12 @@ extern List * PreprocessDropOwnedStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); extern List * PostprocessReassignOwnedStmt(Node *node, const char *queryString); +#if PG_VERSION_NUM >= PG_VERSION_15 + +/* parameter.c - forward declarations */ +extern List * PostprocessGrantParameterStmt(Node *node, const char *queryString); +#endif /* PG_VERSION_NUM >= PG_VERSION_15 */ + /* policy.c - forward declarations */ extern List * CreatePolicyCommands(Oid relationId); extern void ErrorIfUnsupportedPolicy(Relation relation); diff --git a/src/include/distributed/deparser.h b/src/include/distributed/deparser.h index 362f0d59e..a032ab102 100644 --- a/src/include/distributed/deparser.h +++ b/src/include/distributed/deparser.h @@ -259,6 +259,11 @@ extern char * DeparseCreateDatabaseStmt(Node *node); extern char * DeparseDropDatabaseStmt(Node *node); extern char * DeparseAlterDatabaseRenameStmt(Node *node); +#if PG_VERSION_NUM >= PG_VERSION_15 + +/* forward declarations for deparse_parameter_stmts.c*/ +extern char * DeparseGrantOnParameterStmt(Node *node); +#endif /* PG_VERSION_NUM >= PG_VERSION_15 */ /* forward declaration for deparse_publication_stmts.c */ extern char * DeparseCreatePublicationStmt(Node *stmt); diff --git a/src/include/distributed/grant_utils.h b/src/include/distributed/grant_utils.h new file mode 100644 index 000000000..f184b56e8 --- /dev/null +++ b/src/include/distributed/grant_utils.h @@ -0,0 +1,38 @@ +/*------------------------------------------------------------------------- + * + * grant_utils.h + * + * Routines for grant operations. + * + *------------------------------------------------------------------------- + */ +#ifndef CITUS_GRANT_UTILS_H +#define CITUS_GRANT_UTILS_H +#include "postgres.h" + +#include "nodes/parsenodes.h" + +#if PG_VERSION_NUM >= PG_VERSION_15 +extern List * GenerateGrantStmtOnParametersFromCatalogTable(void); +#endif /* PG_VERSION_NUM >= PG_VERSION_15 */ + +extern char * GenerateSetRoleQuery(Oid roleOid); +extern GrantStmt * GenerateGrantStmtForRights(ObjectType objectType, + Oid roleOid, + Oid objectId, + char *permission, + bool withGrantOption); +extern GrantStmt * GenerateGrantStmtForRightsWithObjectName(ObjectType objectType, + Oid roleOid, + char *objectName, + char *permission, + bool withGrantOption); +extern GrantStmt * BaseGenerateGrantStmtForRights(ObjectType objectType, + Oid roleOid, + Oid objectId, + char *objectName, + char *permission, + bool withGrantOption); + + +#endif /* CITUS_GRANT_UTILS_H */ diff --git a/src/test/regress/expected/grant_on_parameter_propagation.out b/src/test/regress/expected/grant_on_parameter_propagation.out new file mode 100644 index 000000000..9c66a4e3f --- /dev/null +++ b/src/test/regress/expected/grant_on_parameter_propagation.out @@ -0,0 +1,254 @@ +-- +-- PG15 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif +create user grant_param_user1; +create user grant_param_user2; +create user grant_param_user3; +create user grant_param_user4; +create user "grant_param_user5-\!"; +--test the grant command with all options +SET citus.log_remote_commands to on; +SET citus.grep_remote_commands = '%GRANT%'; +GRANT SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers TO grant_param_user1,grant_param_user2,"grant_param_user5-\!" WITH GRANT OPTION GRANTED BY CURRENT_USER; +NOTICE: issuing GRANT set, alter system ON PARAMETER max_connections, shared_buffers TO grant_param_user1, grant_param_user2, "grant_param_user5-\!" WITH GRANT OPTION GRANTED BY postgres; +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing GRANT set, alter system ON PARAMETER max_connections, shared_buffers TO grant_param_user1, grant_param_user2, "grant_param_user5-\!" WITH GRANT OPTION GRANTED BY postgres; +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +RESET citus.log_remote_commands; +SELECT check_parameter_privileges(ARRAY['grant_param_user1','grant_param_user2','grant_param_user5-\!'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + check_parameter_privileges +--------------------------------------------------------------------- + (t,grant_param_user1,max_connections,SET) + (t,grant_param_user1,max_connections,SET) + (t,grant_param_user1,max_connections,SET) + (t,grant_param_user1,max_connections,"ALTER SYSTEM") + (t,grant_param_user1,max_connections,"ALTER SYSTEM") + (t,grant_param_user1,max_connections,"ALTER SYSTEM") + (t,grant_param_user1,shared_buffers,SET) + (t,grant_param_user1,shared_buffers,SET) + (t,grant_param_user1,shared_buffers,SET) + (t,grant_param_user1,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user1,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user1,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user2,max_connections,SET) + (t,grant_param_user2,max_connections,SET) + (t,grant_param_user2,max_connections,SET) + (t,grant_param_user2,max_connections,"ALTER SYSTEM") + (t,grant_param_user2,max_connections,"ALTER SYSTEM") + (t,grant_param_user2,max_connections,"ALTER SYSTEM") + (t,grant_param_user2,shared_buffers,SET) + (t,grant_param_user2,shared_buffers,SET) + (t,grant_param_user2,shared_buffers,SET) + (t,grant_param_user2,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user2,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user2,shared_buffers,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",max_connections,SET) + (t,"grant_param_user5-\\!",max_connections,SET) + (t,"grant_param_user5-\\!",max_connections,SET) + (t,"grant_param_user5-\\!",max_connections,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",max_connections,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",max_connections,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",shared_buffers,SET) + (t,"grant_param_user5-\\!",shared_buffers,SET) + (t,"grant_param_user5-\\!",shared_buffers,SET) + (t,"grant_param_user5-\\!",shared_buffers,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",shared_buffers,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",shared_buffers,"ALTER SYSTEM") +(36 rows) + +--test the grant command admin option using grant_param_user1 with granted by +set role grant_param_user1; +SET citus.log_remote_commands to on; +GRANT ALL ON PARAMETER max_connections,shared_buffers TO grant_param_user3 GRANTED BY grant_param_user1; +NOTICE: issuing GRANT ALL PRIVILEGES ON PARAMETER max_connections, shared_buffers TO grant_param_user3 GRANTED BY grant_param_user1; +DETAIL: on server grant_param_user1@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing GRANT ALL PRIVILEGES ON PARAMETER max_connections, shared_buffers TO grant_param_user3 GRANTED BY grant_param_user1; +DETAIL: on server grant_param_user1@localhost:xxxxx connectionId: xxxxxxx +SELECT check_parameter_privileges(ARRAY['grant_param_user3'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + check_parameter_privileges +--------------------------------------------------------------------- + (t,grant_param_user3,max_connections,SET) + (t,grant_param_user3,max_connections,SET) + (t,grant_param_user3,max_connections,SET) + (t,grant_param_user3,max_connections,"ALTER SYSTEM") + (t,grant_param_user3,max_connections,"ALTER SYSTEM") + (t,grant_param_user3,max_connections,"ALTER SYSTEM") + (t,grant_param_user3,shared_buffers,SET) + (t,grant_param_user3,shared_buffers,SET) + (t,grant_param_user3,shared_buffers,SET) + (t,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user3,shared_buffers,"ALTER SYSTEM") +(12 rows) + +reset role; +--test the revoke command grant option with all options +REVOKE GRANT OPTION FOR SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers FROM grant_param_user1,grant_param_user2,"grant_param_user5-\!" cascade; +NOTICE: issuing REVOKE GRANT OPTION FOR set, alter system ON PARAMETER max_connections, shared_buffers FROM grant_param_user1, grant_param_user2, "grant_param_user5-\!" CASCADE; +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing REVOKE GRANT OPTION FOR set, alter system ON PARAMETER max_connections, shared_buffers FROM grant_param_user1, grant_param_user2, "grant_param_user5-\!" CASCADE; +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +--test if the admin option removed for the revoked user. Need to get error +SET ROLE "grant_param_user5-\!"; +GRANT SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers TO grant_param_user3 GRANTED BY "grant_param_user5-\!"; +WARNING: no privileges were granted for "max_connections" +WARNING: no privileges were granted for "shared_buffers" +NOTICE: issuing GRANT set, alter system ON PARAMETER max_connections, shared_buffers TO grant_param_user3 GRANTED BY "grant_param_user5-\!"; +DETAIL: on server grant_param_user5-\!@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing GRANT set, alter system ON PARAMETER max_connections, shared_buffers TO grant_param_user3 GRANTED BY "grant_param_user5-\!"; +DETAIL: on server grant_param_user5-\!@localhost:xxxxx connectionId: xxxxxxx +SELECT check_parameter_privileges(ARRAY['grant_param_user3'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + check_parameter_privileges +--------------------------------------------------------------------- + (f,grant_param_user3,max_connections,SET) + (f,grant_param_user3,max_connections,SET) + (f,grant_param_user3,max_connections,SET) + (f,grant_param_user3,max_connections,"ALTER SYSTEM") + (f,grant_param_user3,max_connections,"ALTER SYSTEM") + (f,grant_param_user3,max_connections,"ALTER SYSTEM") + (f,grant_param_user3,shared_buffers,SET) + (f,grant_param_user3,shared_buffers,SET) + (f,grant_param_user3,shared_buffers,SET) + (f,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user3,shared_buffers,"ALTER SYSTEM") +(12 rows) + +RESET ROLE; +--test the revoke command +REVOKE SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers FROM grant_param_user1,grant_param_user2,grant_param_user3,"grant_param_user5-\!"; +RESET citus.log_remote_commands; +SELECT check_parameter_privileges(ARRAY['grant_param_user1','grant_param_user2','grant_param_user3'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + check_parameter_privileges +--------------------------------------------------------------------- + (f,grant_param_user1,max_connections,SET) + (f,grant_param_user1,max_connections,SET) + (f,grant_param_user1,max_connections,SET) + (f,grant_param_user1,max_connections,"ALTER SYSTEM") + (f,grant_param_user1,max_connections,"ALTER SYSTEM") + (f,grant_param_user1,max_connections,"ALTER SYSTEM") + (f,grant_param_user1,shared_buffers,SET) + (f,grant_param_user1,shared_buffers,SET) + (f,grant_param_user1,shared_buffers,SET) + (f,grant_param_user1,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user1,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user1,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user2,max_connections,SET) + (f,grant_param_user2,max_connections,SET) + (f,grant_param_user2,max_connections,SET) + (f,grant_param_user2,max_connections,"ALTER SYSTEM") + (f,grant_param_user2,max_connections,"ALTER SYSTEM") + (f,grant_param_user2,max_connections,"ALTER SYSTEM") + (f,grant_param_user2,shared_buffers,SET) + (f,grant_param_user2,shared_buffers,SET) + (f,grant_param_user2,shared_buffers,SET) + (f,grant_param_user2,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user2,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user2,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user3,max_connections,SET) + (f,grant_param_user3,max_connections,SET) + (f,grant_param_user3,max_connections,SET) + (f,grant_param_user3,max_connections,"ALTER SYSTEM") + (f,grant_param_user3,max_connections,"ALTER SYSTEM") + (f,grant_param_user3,max_connections,"ALTER SYSTEM") + (f,grant_param_user3,shared_buffers,SET) + (f,grant_param_user3,shared_buffers,SET) + (f,grant_param_user3,shared_buffers,SET) + (f,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user3,shared_buffers,"ALTER SYSTEM") +(36 rows) + +--test with single permission and single user +GRANT ALTER SYSTEM ON PARAMETER max_connections,shared_buffers TO grant_param_user3; +SELECT check_parameter_privileges(ARRAY['grant_param_user4'],ARRAY['max_connections','shared_buffers'], ARRAY['ALTER SYSTEM']); + check_parameter_privileges +--------------------------------------------------------------------- + (f,grant_param_user4,max_connections,"ALTER SYSTEM") + (f,grant_param_user4,max_connections,"ALTER SYSTEM") + (f,grant_param_user4,max_connections,"ALTER SYSTEM") + (f,grant_param_user4,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user4,shared_buffers,"ALTER SYSTEM") + (f,grant_param_user4,shared_buffers,"ALTER SYSTEM") +(6 rows) + +--test metadata_sync +SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +GRANT SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers TO grant_param_user3,"grant_param_user5-\!" WITH GRANT OPTION GRANTED BY CURRENT_USER; +SELECT check_parameter_privileges(ARRAY['grant_param_user3','grant_param_user5-\!'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + check_parameter_privileges +--------------------------------------------------------------------- + (t,grant_param_user3,max_connections,SET) + (t,grant_param_user3,max_connections,SET) + (t,grant_param_user3,max_connections,"ALTER SYSTEM") + (t,grant_param_user3,max_connections,"ALTER SYSTEM") + (t,grant_param_user3,shared_buffers,SET) + (t,grant_param_user3,shared_buffers,SET) + (t,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",max_connections,SET) + (t,"grant_param_user5-\\!",max_connections,SET) + (t,"grant_param_user5-\\!",max_connections,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",max_connections,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",shared_buffers,SET) + (t,"grant_param_user5-\\!",shared_buffers,SET) + (t,"grant_param_user5-\\!",shared_buffers,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",shared_buffers,"ALTER SYSTEM") +(16 rows) + +SELECT 1 FROM citus_add_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT check_parameter_privileges(ARRAY['grant_param_user3','grant_param_user5-\!'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + check_parameter_privileges +--------------------------------------------------------------------- + (t,grant_param_user3,max_connections,SET) + (t,grant_param_user3,max_connections,SET) + (t,grant_param_user3,max_connections,SET) + (t,grant_param_user3,max_connections,"ALTER SYSTEM") + (t,grant_param_user3,max_connections,"ALTER SYSTEM") + (t,grant_param_user3,max_connections,"ALTER SYSTEM") + (t,grant_param_user3,shared_buffers,SET) + (t,grant_param_user3,shared_buffers,SET) + (t,grant_param_user3,shared_buffers,SET) + (t,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (t,grant_param_user3,shared_buffers,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",max_connections,SET) + (t,"grant_param_user5-\\!",max_connections,SET) + (t,"grant_param_user5-\\!",max_connections,SET) + (t,"grant_param_user5-\\!",max_connections,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",max_connections,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",max_connections,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",shared_buffers,SET) + (t,"grant_param_user5-\\!",shared_buffers,SET) + (t,"grant_param_user5-\\!",shared_buffers,SET) + (t,"grant_param_user5-\\!",shared_buffers,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",shared_buffers,"ALTER SYSTEM") + (t,"grant_param_user5-\\!",shared_buffers,"ALTER SYSTEM") +(24 rows) + +REVOKE SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers FROM grant_param_user3,"grant_param_user5-\!" cascade; +--clean all resources +DROP USER grant_param_user1; +DROP USER grant_param_user2; +DROP USER grant_param_user3; +DROP USER grant_param_user4; +DROP USER "grant_param_user5-\!"; +reset citus.log_remote_commands; +reset citus.grep_remote_commands; diff --git a/src/test/regress/expected/grant_on_parameter_propagation_0.out b/src/test/regress/expected/grant_on_parameter_propagation_0.out new file mode 100644 index 000000000..b1ed9cc5b --- /dev/null +++ b/src/test/regress/expected/grant_on_parameter_propagation_0.out @@ -0,0 +1,9 @@ +-- +-- PG15 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q diff --git a/src/test/regress/expected/multi_test_helpers.out b/src/test/regress/expected/multi_test_helpers.out index 957a3d11b..167696ca2 100644 --- a/src/test/regress/expected/multi_test_helpers.out +++ b/src/test/regress/expected/multi_test_helpers.out @@ -628,6 +628,25 @@ BEGIN JOIN pg_dist_node USING (nodeid); END; $func$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION check_parameter_privileges(users text[], parameters text[], permissions text[]) +RETURNS TABLE ( res text, usr text, param text, perms text) AS $func$ +DECLARE + u text; + p text; + perm text; +BEGIN + FOREACH u IN ARRAY users + LOOP + FOREACH p IN ARRAY parameters + LOOP + FOREACH perm IN ARRAY permissions + LOOP + RETURN QUERY EXECUTE format($inner$SELECT result ,'%1$s','%2$s','%3$s' FROM run_command_on_all_nodes($$SELECT has_parameter_privilege('%1$s','%2$s', '%3$s'); $$)$inner$, u, p, perm); + END LOOP; + END LOOP; + END LOOP; +END; +$func$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION check_database_privileges(role_name text, db_name text, permissions text[]) RETURNS TABLE(permission text, result text) AS $func$ diff --git a/src/test/regress/multi_1_schedule b/src/test/regress/multi_1_schedule index 2ce74e9a7..0b34f5081 100644 --- a/src/test/regress/multi_1_schedule +++ b/src/test/regress/multi_1_schedule @@ -38,6 +38,7 @@ test: create_single_shard_table test: create_drop_database_propagation test: create_drop_database_propagation_pg15 test: create_drop_database_propagation_pg16 +test: grant_on_parameter_propagation test: comment_on_database test: comment_on_role # don't parallelize single_shard_table_udfs to make sure colocation ids are sequential diff --git a/src/test/regress/sql/grant_on_parameter_propagation.sql b/src/test/regress/sql/grant_on_parameter_propagation.sql new file mode 100644 index 000000000..072aa70c7 --- /dev/null +++ b/src/test/regress/sql/grant_on_parameter_propagation.sql @@ -0,0 +1,81 @@ +-- +-- PG15 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +\else +\q +\endif + +create user grant_param_user1; +create user grant_param_user2; +create user grant_param_user3; +create user grant_param_user4; +create user "grant_param_user5-\!"; + + +--test the grant command with all options +SET citus.log_remote_commands to on; +SET citus.grep_remote_commands = '%GRANT%'; +GRANT SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers TO grant_param_user1,grant_param_user2,"grant_param_user5-\!" WITH GRANT OPTION GRANTED BY CURRENT_USER; + +RESET citus.log_remote_commands; +SELECT check_parameter_privileges(ARRAY['grant_param_user1','grant_param_user2','grant_param_user5-\!'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + +--test the grant command admin option using grant_param_user1 with granted by +set role grant_param_user1; +SET citus.log_remote_commands to on; +GRANT ALL ON PARAMETER max_connections,shared_buffers TO grant_param_user3 GRANTED BY grant_param_user1; +SELECT check_parameter_privileges(ARRAY['grant_param_user3'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + +reset role; + +--test the revoke command grant option with all options +REVOKE GRANT OPTION FOR SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers FROM grant_param_user1,grant_param_user2,"grant_param_user5-\!" cascade; + +--test if the admin option removed for the revoked user. Need to get error +SET ROLE "grant_param_user5-\!"; +GRANT SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers TO grant_param_user3 GRANTED BY "grant_param_user5-\!"; + +SELECT check_parameter_privileges(ARRAY['grant_param_user3'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + +RESET ROLE; + +--test the revoke command +REVOKE SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers FROM grant_param_user1,grant_param_user2,grant_param_user3,"grant_param_user5-\!"; + +RESET citus.log_remote_commands; + +SELECT check_parameter_privileges(ARRAY['grant_param_user1','grant_param_user2','grant_param_user3'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + + +--test with single permission and single user +GRANT ALTER SYSTEM ON PARAMETER max_connections,shared_buffers TO grant_param_user3; + +SELECT check_parameter_privileges(ARRAY['grant_param_user4'],ARRAY['max_connections','shared_buffers'], ARRAY['ALTER SYSTEM']); + +--test metadata_sync + +SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); +GRANT SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers TO grant_param_user3,"grant_param_user5-\!" WITH GRANT OPTION GRANTED BY CURRENT_USER; + +SELECT check_parameter_privileges(ARRAY['grant_param_user3','grant_param_user5-\!'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + +SELECT 1 FROM citus_add_node('localhost', :worker_2_port); + +SELECT check_parameter_privileges(ARRAY['grant_param_user3','grant_param_user5-\!'],ARRAY['max_connections','shared_buffers'], ARRAY['SET','ALTER SYSTEM']); + +REVOKE SET,ALTER SYSTEM ON PARAMETER max_connections,shared_buffers FROM grant_param_user3,"grant_param_user5-\!" cascade; + + +--clean all resources +DROP USER grant_param_user1; +DROP USER grant_param_user2; +DROP USER grant_param_user3; +DROP USER grant_param_user4; +DROP USER "grant_param_user5-\!"; + +reset citus.log_remote_commands; +reset citus.grep_remote_commands; diff --git a/src/test/regress/sql/multi_test_helpers.sql b/src/test/regress/sql/multi_test_helpers.sql index 10242692c..d10e7ba1b 100644 --- a/src/test/regress/sql/multi_test_helpers.sql +++ b/src/test/regress/sql/multi_test_helpers.sql @@ -655,6 +655,26 @@ BEGIN END; $func$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION check_parameter_privileges(users text[], parameters text[], permissions text[]) +RETURNS TABLE ( res text, usr text, param text, perms text) AS $func$ +DECLARE + u text; + p text; + perm text; +BEGIN + FOREACH u IN ARRAY users + LOOP + FOREACH p IN ARRAY parameters + LOOP + FOREACH perm IN ARRAY permissions + LOOP + RETURN QUERY EXECUTE format($inner$SELECT result ,'%1$s','%2$s','%3$s' FROM run_command_on_all_nodes($$SELECT has_parameter_privilege('%1$s','%2$s', '%3$s'); $$)$inner$, u, p, perm); + END LOOP; + END LOOP; + END LOOP; +END; +$func$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION check_database_privileges(role_name text, db_name text, permissions text[]) RETURNS TABLE(permission text, result text) AS $func$