diff --git a/src/backend/distributed/commands/role.c b/src/backend/distributed/commands/role.c new file mode 100644 index 000000000..61ed0496e --- /dev/null +++ b/src/backend/distributed/commands/role.c @@ -0,0 +1,281 @@ +/*------------------------------------------------------------------------- + * + * role.c + * Commands for ALTER ROLE statements. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup_details.h" +#if PG_VERSION_NUM >= 120000 +#include "access/table.h" +#endif +#include "catalog/catalog.h" +#include "catalog/pg_authid.h" +#include "distributed/citus_ruleutils.h" +#include "distributed/commands.h" +#include "distributed/commands/utility_hook.h" +#include "distributed/deparser.h" +#include "distributed/master_protocol.h" +#include "distributed/worker_transaction.h" +#include "nodes/makefuncs.h" +#include "nodes/parsenodes.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +static const char * ExtractEncryptedPassword(Oid roleOid); +static const char * CreateAlterRoleIfExistsCommand(AlterRoleStmt *stmt); +static DefElem * makeDefElemInt(char *name, int value); + +/* controlled via GUC */ +bool EnableAlterRolePropagation = false; + +/* + * ProcessAlterRoleStmt actually creates the plan we need to execute for alter + * role statement. We need to do it this way because we need to use the encrypted + * password, which is, in some cases, created at standardProcessUtility. + */ +List * +ProcessAlterRoleStmt(AlterRoleStmt *stmt, const char *queryString) +{ + ListCell *optionCell = NULL; + List *commands = NIL; + + if (!EnableAlterRolePropagation || !IsCoordinator()) + { + return NIL; + } + + /* + * Make sure that no new nodes are added after this point until the end of the + * transaction by taking a RowShareLock on pg_dist_node, which conflicts with the + * ExclusiveLock taken by master_add_node. + */ + LockRelationOid(DistNodeRelationId(), RowShareLock); + + foreach(optionCell, stmt->options) + { + DefElem *option = (DefElem *) lfirst(optionCell); + + if (strcasecmp(option->defname, "password") == 0) + { + Oid roleOid = get_rolespec_oid(stmt->role, true); + const char *encryptedPassword = ExtractEncryptedPassword(roleOid); + + if (encryptedPassword != NULL) + { + Value *encryptedPasswordValue = makeString((char *) encryptedPassword); + option->arg = (Node *) encryptedPasswordValue; + } + else + { + option->arg = NULL; + } + + break; + } + } + commands = list_make1((void *) CreateAlterRoleIfExistsCommand(stmt)); + + return NodeDDLTaskList(ALL_WORKERS, commands); +} + + +/* + * CreateAlterRoleIfExistsCommand creates ALTER ROLE command, from the alter role node + * using the alter_role_if_exists() UDF. + */ +static const char * +CreateAlterRoleIfExistsCommand(AlterRoleStmt *stmt) +{ + StringInfoData alterRoleQueryBuffer = { 0 }; + const char *roleName = RoleSpecString(stmt->role, false); + const char *alterRoleQuery = DeparseTreeNode((Node *) stmt); + + initStringInfo(&alterRoleQueryBuffer); + appendStringInfo(&alterRoleQueryBuffer, + "SELECT alter_role_if_exists(%s, %s)", + quote_literal_cstr(roleName), + quote_literal_cstr(alterRoleQuery)); + + return alterRoleQueryBuffer.data; +} + + +/* + * ExtractEncryptedPassword extracts the encrypted password of a role. The function + * gets the password from the pg_authid table. + */ +static const char * +ExtractEncryptedPassword(Oid roleOid) +{ + Relation pgAuthId = heap_open(AuthIdRelationId, AccessShareLock); + TupleDesc pgAuthIdDescription = RelationGetDescr(pgAuthId); + HeapTuple tuple = SearchSysCache1(AUTHOID, roleOid); + bool isNull = true; + Datum passwordDatum; + + if (!HeapTupleIsValid(tuple)) + { + return NULL; + } + + passwordDatum = heap_getattr(tuple, Anum_pg_authid_rolpassword, + pgAuthIdDescription, &isNull); + + heap_close(pgAuthId, AccessShareLock); + ReleaseSysCache(tuple); + + if (isNull) + { + return NULL; + } + + return pstrdup(TextDatumGetCString(passwordDatum)); +} + + +/* + * GenerateAlterRoleIfExistsCommand generate ALTER ROLE command that copies a role from + * the pg_authid table. + */ +static const char * +GenerateAlterRoleIfExistsCommand(HeapTuple tuple, TupleDesc pgAuthIdDescription) +{ + char *rolPassword = ""; + char *rolValidUntil = "infinity"; + Datum rolValidUntilDatum; + Datum rolPasswordDatum; + bool isNull = true; + Form_pg_authid role = ((Form_pg_authid) GETSTRUCT(tuple)); + AlterRoleStmt *stmt = makeNode(AlterRoleStmt); + const char *rolename = NameStr(role->rolname); + + stmt->role = makeNode(RoleSpec); + stmt->role->roletype = ROLESPEC_CSTRING; + stmt->role->location = -1; + stmt->role->rolename = pstrdup(rolename); + stmt->action = 1; + stmt->options = NIL; + + stmt->options = + lappend(stmt->options, + makeDefElemInt("superuser", role->rolsuper)); + + stmt->options = + lappend(stmt->options, + makeDefElemInt("createdb", role->rolcreatedb)); + + stmt->options = + lappend(stmt->options, + makeDefElemInt("createrole", role->rolcreaterole)); + + stmt->options = + lappend(stmt->options, + makeDefElemInt("inherit", role->rolinherit)); + + stmt->options = + lappend(stmt->options, + makeDefElemInt("canlogin", role->rolcanlogin)); + + stmt->options = + lappend(stmt->options, + makeDefElemInt("isreplication", role->rolreplication)); + + stmt->options = + lappend(stmt->options, + makeDefElemInt("bypassrls", role->rolbypassrls)); + + + stmt->options = + lappend(stmt->options, + makeDefElemInt("connectionlimit", role->rolconnlimit)); + + + rolPasswordDatum = heap_getattr(tuple, Anum_pg_authid_rolpassword, + pgAuthIdDescription, &isNull); + if (!isNull) + { + rolPassword = pstrdup(TextDatumGetCString(rolPasswordDatum)); + stmt->options = lappend(stmt->options, makeDefElem("password", + (Node *) makeString( + rolPassword), + -1)); + } + else + { + stmt->options = lappend(stmt->options, makeDefElem("password", NULL, -1)); + } + + rolValidUntilDatum = heap_getattr(tuple, Anum_pg_authid_rolvaliduntil, + pgAuthIdDescription, &isNull); + if (!isNull) + { + rolValidUntil = pstrdup((char *) timestamptz_to_str(rolValidUntilDatum)); + } + + stmt->options = lappend(stmt->options, makeDefElem("validUntil", + (Node *) makeString(rolValidUntil), + -1)); + + return CreateAlterRoleIfExistsCommand(stmt); +} + + +/* + * GenerateAlterRoleIfExistsCommandAllRoles creates ALTER ROLE commands + * that copies all roles from the pg_authid table. + */ +List * +GenerateAlterRoleIfExistsCommandAllRoles() +{ + Relation pgAuthId = heap_open(AuthIdRelationId, AccessShareLock); + TupleDesc pgAuthIdDescription = RelationGetDescr(pgAuthId); + HeapTuple tuple = NULL; + List *commands = NIL; + const char *alterRoleQuery = NULL; + +#if PG_VERSION_NUM >= 120000 + TableScanDesc scan = table_beginscan_catalog(pgAuthId, 0, NULL); +#else + HeapScanDesc scan = heap_beginscan_catalog(pgAuthId, 0, NULL); +#endif + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + const char *rolename = NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname); + + /* + * The default roles are skipped, because reserved roles + * cannot be altered. + */ + if (IsReservedName(rolename)) + { + continue; + } + alterRoleQuery = GenerateAlterRoleIfExistsCommand(tuple, pgAuthIdDescription); + commands = lappend(commands, (void *) alterRoleQuery); + } + + heap_endscan(scan); + heap_close(pgAuthId, AccessShareLock); + + return commands; +} + + +/* + * makeDefElemInt creates a DefElem with integer typed value with -1 as location. + */ +static DefElem * +makeDefElemInt(char *name, int value) +{ + return makeDefElem(name, (Node *) makeInteger(value), -1); +} diff --git a/src/backend/distributed/commands/utility_hook.c b/src/backend/distributed/commands/utility_hook.c index 3d8d01e95..afab20f82 100644 --- a/src/backend/distributed/commands/utility_hook.c +++ b/src/backend/distributed/commands/utility_hook.c @@ -739,6 +739,38 @@ multi_ProcessUtility(PlannedStmt *pstmt, ddlJobs = ProcessCreateFunctionStmt(castNode(CreateFunctionStmt, parsetree), queryString); } + + if (IsA(parsetree, AlterRoleStmt)) + { + Assert(ddlJobs == NIL); /* jobs should not have been set before */ + ddlJobs = ProcessAlterRoleStmt(castNode(AlterRoleStmt, parsetree), + queryString); + } + + if (IsA(parsetree, AlterRoleSetStmt) && EnableAlterRolePropagation) + { + ereport(NOTICE, (errmsg("Citus partially supports ALTER ROLE for " + "distributed databases"), + + errdetail( + "Citus does not propagate ALTER ROLE ... SET/RESET " + "commands to workers"), + + errhint("You can manually alter roles on workers."))); + } + + if (IsA(parsetree, RenameStmt) && ((RenameStmt *) parsetree)->renameType == + OBJECT_ROLE && EnableAlterRolePropagation) + { + ereport(NOTICE, (errmsg("Citus partially supports ALTER ROLE for " + "distributed databases"), + + errdetail( + "Citus does not propagate ALTER ROLE ... RENAME TO " + "commands to workers"), + + errhint("You can manually alter roles on workers."))); + } } /* diff --git a/src/backend/distributed/deparser/deparse.c b/src/backend/distributed/deparser/deparse.c index 53ed2c325..dcf2807ce 100644 --- a/src/backend/distributed/deparser/deparse.c +++ b/src/backend/distributed/deparser/deparse.c @@ -93,6 +93,11 @@ DeparseTreeNode(Node *stmt) return DeparseAlterObjectDependsStmt(castNode(AlterObjectDependsStmt, stmt)); } + case T_AlterRoleStmt: + { + return DeparseAlterRoleStmt(castNode(AlterRoleStmt, stmt)); + } + default: { ereport(ERROR, (errmsg("unsupported statement for deparsing"))); diff --git a/src/backend/distributed/deparser/deparse_function_stmts.c b/src/backend/distributed/deparser/deparse_function_stmts.c index 957300010..224320570 100644 --- a/src/backend/distributed/deparser/deparse_function_stmts.c +++ b/src/backend/distributed/deparser/deparse_function_stmts.c @@ -402,7 +402,7 @@ AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt) appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objectType)); AppendFunctionName(buf, func, stmt->objectType); - appendStringInfo(buf, " OWNER TO %s;", RoleSpecString(stmt->newowner)); + appendStringInfo(buf, " OWNER TO %s;", RoleSpecString(stmt->newowner, true)); } diff --git a/src/backend/distributed/deparser/deparse_role_stmts.c b/src/backend/distributed/deparser/deparse_role_stmts.c new file mode 100644 index 000000000..962d0539b --- /dev/null +++ b/src/backend/distributed/deparser/deparse_role_stmts.c @@ -0,0 +1,139 @@ +/*------------------------------------------------------------------------- + * + * deparse_role_stmts.c + * All routines to deparse role statements. + * This file contains all entry points specific for ALTER ROLE statement + * deparsing as well as functions that are currently only used for deparsing + * ALTER ROLE statements. + * + * Copyright (c), Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "distributed/citus_ruleutils.h" +#include "distributed/deparser.h" +#include "lib/stringinfo.h" +#include "nodes/parsenodes.h" +#include "utils/builtins.h" + +static void AppendAlterRoleStmt(StringInfo buf, AlterRoleStmt *stmt); + + +/* + * DeparseAlterRoleStmt builds and returns a string representing of the + * AlterRoleStmt for application on a remote server. + */ +const char * +DeparseAlterRoleStmt(AlterRoleStmt *stmt) +{ + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + AppendAlterRoleStmt(&buf, stmt); + + return buf.data; +} + + +/* + * AppendAlterRoleStmt generates the string representation of the + * AlterRoleStmt and appends it to the buffer. + */ +static void +AppendAlterRoleStmt(StringInfo buf, AlterRoleStmt *stmt) +{ + ListCell *optionCell = NULL; + RoleSpec *role = stmt->role; + + /* + * If the role_specification used is CURRENT_USER or SESSION_USER, + * it will be converted to thats roles role name. + */ + appendStringInfo(buf, "ALTER ROLE %s", RoleSpecString(role, true)); + + foreach(optionCell, stmt->options) + { + DefElem *option = (DefElem *) lfirst(optionCell); + + if (strcmp(option->defname, "superuser") == 0 && intVal(option->arg)) + { + appendStringInfo(buf, " SUPERUSER"); + } + else if (strcmp(option->defname, "superuser") == 0 && !intVal(option->arg)) + { + appendStringInfo(buf, " NOSUPERUSER"); + } + else if (strcmp(option->defname, "createdb") == 0 && intVal(option->arg)) + { + appendStringInfo(buf, " CREATEDB"); + } + else if (strcmp(option->defname, "createdb") == 0 && !intVal(option->arg)) + { + appendStringInfo(buf, " NOCREATEDB"); + } + else if (strcmp(option->defname, "createrole") == 0 && intVal(option->arg)) + { + appendStringInfo(buf, " CREATEROLE"); + } + else if (strcmp(option->defname, "createrole") == 0 && !intVal(option->arg)) + { + appendStringInfo(buf, " NOCREATEROLE"); + } + else if (strcmp(option->defname, "inherit") == 0 && intVal(option->arg)) + { + appendStringInfo(buf, " INHERIT"); + } + else if (strcmp(option->defname, "inherit") == 0 && !intVal(option->arg)) + { + appendStringInfo(buf, " NOINHERIT"); + } + else if (strcmp(option->defname, "canlogin") == 0 && intVal(option->arg)) + { + appendStringInfo(buf, " LOGIN"); + } + else if (strcmp(option->defname, "canlogin") == 0 && !intVal(option->arg)) + { + appendStringInfo(buf, " NOLOGIN"); + } + else if (strcmp(option->defname, "isreplication") == 0 && intVal(option->arg)) + { + appendStringInfo(buf, " REPLICATION"); + } + else if (strcmp(option->defname, "isreplication") == 0 && !intVal(option->arg)) + { + appendStringInfo(buf, " NOREPLICATION"); + } + else if (strcmp(option->defname, "bypassrls") == 0 && intVal(option->arg)) + { + appendStringInfo(buf, " BYPASSRLS"); + } + else if (strcmp(option->defname, "bypassrls") == 0 && !intVal(option->arg)) + { + appendStringInfo(buf, " NOBYPASSRLS"); + } + else if (strcmp(option->defname, "connectionlimit") == 0) + { + appendStringInfo(buf, " CONNECTION LIMIT %d", intVal(option->arg)); + } + else if (strcmp(option->defname, "password") == 0) + { + if (option->arg != NULL) + { + appendStringInfo(buf, " PASSWORD %s", quote_literal_cstr(strVal( + option->arg))); + } + else + { + appendStringInfo(buf, " PASSWORD NULL"); + } + } + else if (strcmp(option->defname, "validUntil") == 0) + { + appendStringInfo(buf, " VALID UNTIL %s", quote_literal_cstr(strVal( + option->arg))); + } + } +} diff --git a/src/backend/distributed/deparser/deparse_type_stmts.c b/src/backend/distributed/deparser/deparse_type_stmts.c index b1ba56254..a667d6caf 100644 --- a/src/backend/distributed/deparser/deparse_type_stmts.c +++ b/src/backend/distributed/deparser/deparse_type_stmts.c @@ -505,5 +505,5 @@ AppendAlterTypeOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt) names = (List *) stmt->object; appendStringInfo(buf, "ALTER TYPE %s OWNER TO %s;", NameListToQuotedString(names), - RoleSpecString(stmt->newowner)); + RoleSpecString(stmt->newowner, true)); } diff --git a/src/backend/distributed/shared_library_init.c b/src/backend/distributed/shared_library_init.c index 16d5ff735..fa53748df 100644 --- a/src/backend/distributed/shared_library_init.c +++ b/src/backend/distributed/shared_library_init.c @@ -730,6 +730,16 @@ RegisterCitusConfigVariables(void) GUC_NO_SHOW_ALL, NULL, NULL, NULL); + DefineCustomBoolVariable( + "citus.enable_alter_role_propagation", + gettext_noop("Enables propagating role passwords statements to workers"), + NULL, + &EnableAlterRolePropagation, + false, + PGC_USERSET, + GUC_STANDARD, + NULL, NULL, NULL); + DefineCustomEnumVariable( "citus.propagate_set_commands", gettext_noop("Sets which SET commands are propagated to workers."), diff --git a/src/backend/distributed/sql/citus--9.0-2--9.1-1.sql b/src/backend/distributed/sql/citus--9.0-2--9.1-1.sql index 253d63e77..743b9a42d 100644 --- a/src/backend/distributed/sql/citus--9.0-2--9.1-1.sql +++ b/src/backend/distributed/sql/citus--9.0-2--9.1-1.sql @@ -8,6 +8,7 @@ COMMENT ON COLUMN pg_catalog.pg_dist_node.shouldhaveshards IS #include "udfs/get_rebalance_table_shards_plan/9.1-1.sql" #include "udfs/master_add_node/9.1-1.sql" #include "udfs/master_add_inactive_node/9.1-1.sql" +#include "udfs/alter_role_if_exists/9.1-1.sql" -- we don't maintain replication factor of reference tables anymore and just -- use -1 instead. @@ -70,4 +71,3 @@ CREATE AGGREGATE citus.coord_combine_agg(oid, cstring, anyelement) ( ); COMMENT ON AGGREGATE citus.coord_combine_agg(oid, cstring, anyelement) IS 'support aggregate for implementing combining partial aggregate results from workers'; - diff --git a/src/backend/distributed/sql/udfs/alter_role_if_exists/9.1-1.sql b/src/backend/distributed/sql/udfs/alter_role_if_exists/9.1-1.sql new file mode 100644 index 000000000..e9edcc656 --- /dev/null +++ b/src/backend/distributed/sql/udfs/alter_role_if_exists/9.1-1.sql @@ -0,0 +1,12 @@ +CREATE OR REPLACE FUNCTION pg_catalog.alter_role_if_exists( + role_name text, + utility_query text) + RETURNS BOOL + LANGUAGE C STRICT + AS 'MODULE_PATHNAME', $$alter_role_if_exists$$; + +COMMENT ON FUNCTION pg_catalog.alter_role_if_exists( + role_name text, + utility_query text) + IS 'runs the utility query, if the role exists'; + \ No newline at end of file diff --git a/src/backend/distributed/sql/udfs/alter_role_if_exists/latest.sql b/src/backend/distributed/sql/udfs/alter_role_if_exists/latest.sql new file mode 100644 index 000000000..e9edcc656 --- /dev/null +++ b/src/backend/distributed/sql/udfs/alter_role_if_exists/latest.sql @@ -0,0 +1,12 @@ +CREATE OR REPLACE FUNCTION pg_catalog.alter_role_if_exists( + role_name text, + utility_query text) + RETURNS BOOL + LANGUAGE C STRICT + AS 'MODULE_PATHNAME', $$alter_role_if_exists$$; + +COMMENT ON FUNCTION pg_catalog.alter_role_if_exists( + role_name text, + utility_query text) + IS 'runs the utility query, if the role exists'; + \ No newline at end of file diff --git a/src/backend/distributed/utils/citus_ruleutils.c b/src/backend/distributed/utils/citus_ruleutils.c index 371db2612..2f46296c4 100644 --- a/src/backend/distributed/utils/citus_ruleutils.c +++ b/src/backend/distributed/utils/citus_ruleutils.c @@ -1371,25 +1371,34 @@ simple_quote_literal(StringInfo buf, const char *val) * * CURRENT_USER - resolved to the user name of the current role being used * SESSION_USER - resolved to the user name of the user that opened the session + * + * withQuoteIdendifier is used, because if the results will be used in a query the quotes are needed but if not there + * should not be extra quotes. */ const char * -RoleSpecString(RoleSpec *spec) +RoleSpecString(RoleSpec *spec, bool withQuoteIdendifier) { switch (spec->roletype) { case ROLESPEC_CSTRING: { - return quote_identifier(spec->rolename); + return withQuoteIdendifier ? + quote_identifier(spec->rolename) : + spec->rolename; } case ROLESPEC_CURRENT_USER: { - return quote_identifier(GetUserNameFromId(GetUserId(), false)); + return withQuoteIdendifier ? + quote_identifier(GetUserNameFromId(GetUserId(), false)) : + GetUserNameFromId(GetUserId(), false); } case ROLESPEC_SESSION_USER: { - return quote_identifier(GetUserNameFromId(GetSessionUserId(), false)); + return withQuoteIdendifier ? + quote_identifier(GetUserNameFromId(GetSessionUserId(), false)) : + GetUserNameFromId(GetSessionUserId(), false); } case ROLESPEC_PUBLIC: diff --git a/src/backend/distributed/utils/node_metadata.c b/src/backend/distributed/utils/node_metadata.c index e9b89562a..0a4237bee 100644 --- a/src/backend/distributed/utils/node_metadata.c +++ b/src/backend/distributed/utils/node_metadata.c @@ -22,6 +22,8 @@ #include "commands/sequence.h" #include "distributed/citus_acquire_lock.h" #include "distributed/colocation_utils.h" +#include "distributed/commands.h" +#include "distributed/commands/utility_hook.h" #include "distributed/connection_management.h" #include "distributed/maintenanced.h" #include "distributed/master_protocol.h" @@ -388,6 +390,28 @@ SetUpDistributedTableDependencies(WorkerNode *newWorkerNode) } +/* + * PropagateRolesToNewNode copies the roles' attributes in the new node. Roles that do + * not exist in the workers are not created and simply skipped. + */ +static void +PropagateRolesToNewNode(WorkerNode *newWorkerNode) +{ + List *ddlCommands = NIL; + + if (!EnableAlterRolePropagation) + { + return; + } + + ddlCommands = GenerateAlterRoleIfExistsCommandAllRoles(); + + SendCommandListToWorkerInSingleTransaction(newWorkerNode->workerName, + newWorkerNode->workerPort, + CitusExtensionOwnerName(), ddlCommands); +} + + /* * ModifiableWorkerNode gets the requested WorkerNode and also gets locks * required for modifying it. This fails if the node does not exist. @@ -565,6 +589,7 @@ ActivateNode(char *nodeName, int nodePort) newWorkerNode = SetNodeState(nodeName, nodePort, isActive); + PropagateRolesToNewNode(newWorkerNode); SetUpDistributedTableDependencies(newWorkerNode); return newWorkerNode->nodeId; } diff --git a/src/backend/distributed/utils/role.c b/src/backend/distributed/utils/role.c new file mode 100644 index 000000000..0490857c9 --- /dev/null +++ b/src/backend/distributed/utils/role.c @@ -0,0 +1,48 @@ +/*------------------------------------------------------------------------- + * + * role.c + * + * Utilities for ALTER ROLE statments. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "distributed/commands/utility_hook.h" +#include "distributed/worker_protocol.h" +#include "fmgr.h" +#include "tcop/dest.h" +#include "tcop/utility.h" +#include "utils/builtins.h" + +PG_FUNCTION_INFO_V1(alter_role_if_exists); + +/* + * alter_role_if_exists checks if the role, whose name is given + * in the first parameter exists and then runs the query, which is the second + * parameter. This UDF is particularly used for ALTER ROLE queries, how ever it + * can run any other query too. + */ +Datum +alter_role_if_exists(PG_FUNCTION_ARGS) +{ + text *rolenameText = PG_GETARG_TEXT_P(0); + const char *rolename = text_to_cstring(rolenameText); + text *utilityQueryText = PG_GETARG_TEXT_P(1); + const char *utilityQuery = text_to_cstring(utilityQueryText); + Node *parseTree = NULL; + + if (get_role_oid(rolename, true) == InvalidOid) + { + PG_RETURN_BOOL(false); + } + + parseTree = ParseTreeNode(utilityQuery); + + CitusProcessUtility(parseTree, utilityQuery, PROCESS_UTILITY_TOPLEVEL, NULL, + None_Receiver, NULL); + + PG_RETURN_BOOL(true); +} diff --git a/src/include/distributed/citus_ruleutils.h b/src/include/distributed/citus_ruleutils.h index 470f45726..407d33f4c 100644 --- a/src/include/distributed/citus_ruleutils.h +++ b/src/include/distributed/citus_ruleutils.h @@ -41,7 +41,7 @@ extern char * pg_get_indexclusterdef_string(Oid indexRelationId); extern List * pg_get_table_grants(Oid relationId); extern bool contain_nextval_expression_walker(Node *node, void *context); extern char * pg_get_replica_identity_command(Oid tableRelationId); -extern const char * RoleSpecString(RoleSpec *spec); +extern const char * RoleSpecString(RoleSpec *spec, bool withQuoteIdendifier); /* Function declarations for version dependent PostgreSQL ruleutils functions */ extern void pg_get_query_def(Query *query, StringInfo buffer); diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index 46564385f..53b8c4a0b 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -116,6 +116,11 @@ extern List * PlanRenameStmt(RenameStmt *renameStmt, const char *renameCommand); extern void ErrorIfUnsupportedRenameStmt(RenameStmt *renameStmt); +/* role.c - forward declarations*/ +extern List * ProcessAlterRoleStmt(AlterRoleStmt *stmt, const char *queryString); +extern List * GenerateAlterRoleIfExistsCommandAllRoles(void); + + /* schema.c - forward declarations */ extern void ProcessDropSchemaStmt(DropStmt *dropSchemaStatement); extern List * PlanAlterTableSchemaStmt(AlterObjectSchemaStmt *stmt, diff --git a/src/include/distributed/commands/utility_hook.h b/src/include/distributed/commands/utility_hook.h index 359bcd176..2dbf8d243 100644 --- a/src/include/distributed/commands/utility_hook.h +++ b/src/include/distributed/commands/utility_hook.h @@ -30,6 +30,7 @@ extern PropSetCmdBehavior PropagateSetCommands; extern bool EnableDDLPropagation; extern bool EnableDependencyCreation; extern bool EnableCreateTypePropagation; +extern bool EnableAlterRolePropagation; /* * A DDLJob encapsulates the remote tasks and commands needed to process all or diff --git a/src/include/distributed/deparser.h b/src/include/distributed/deparser.h index 6a64931f3..a659432f3 100644 --- a/src/include/distributed/deparser.h +++ b/src/include/distributed/deparser.h @@ -69,4 +69,8 @@ extern void QualifyAlterFunctionSchemaStmt(AlterObjectSchemaStmt *stmt); extern void QualifyAlterFunctionOwnerStmt(AlterOwnerStmt *stmt); extern void QualifyAlterFunctionDependsStmt(AlterObjectDependsStmt *stmt); + +/* forward declarations for deparse_role_stmts.c */ +extern const char * DeparseAlterRoleStmt(AlterRoleStmt *stmt); + #endif /* CITUS_DEPARSER_H */ diff --git a/src/test/regress/expected/alter_role_propagation.out b/src/test/regress/expected/alter_role_propagation.out new file mode 100644 index 000000000..745a4824a --- /dev/null +++ b/src/test/regress/expected/alter_role_propagation.out @@ -0,0 +1,253 @@ +CREATE SCHEMA alter_role; +SET citus.enable_alter_role_propagation to ON; +CREATE ROLE alter_role_1 WITH LOGIN; +NOTICE: not propagating CREATE ROLE/USER commands to worker nodes +HINT: Connect to worker nodes directly to manually create all necessary users and roles. +SELECT run_command_on_workers($$CREATE ROLE alter_role_1 WITH LOGIN;$$); + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE ROLE") + (localhost,57638,t,"CREATE ROLE") +(2 rows) + +-- postgres errors out +ALTER ROLE alter_role_1 WITH SUPERUSER NOSUPERUSER; +ERROR: conflicting or redundant options +-- make sure that we propagate all options accurately +ALTER ROLE alter_role_1 WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 66 VALID UNTIL '2032-05-05'; +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; + row +--------------------------------------- + (alter_role_1,t,t,t,t,t,t,t,66,,2032) +(1 row) + +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +------------------------------------------------------------- + (localhost,57637,t,"(alter_role_1,t,t,t,t,t,t,t,66,,2032)") + (localhost,57638,t,"(alter_role_1,t,t,t,t,t,t,t,66,,2032)") +(2 rows) + +-- make sure that we propagate all options accurately +ALTER ROLE alter_role_1 WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 0 VALID UNTIL '2052-05-05'; +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; + row +-------------------------------------- + (alter_role_1,f,f,f,f,f,f,f,0,,2052) +(1 row) + +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +------------------------------------------------------------ + (localhost,57637,t,"(alter_role_1,f,f,f,f,f,f,f,0,,2052)") + (localhost,57638,t,"(alter_role_1,f,f,f,f,f,f,f,0,,2052)") +(2 rows) + +-- make sure that non-existent users are handled properly +ALTER ROLE alter_role_2 WITH SUPERUSER NOSUPERUSER; +ERROR: conflicting or redundant options +ALTER ROLE alter_role_2 WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 66 VALID UNTIL '2032-05-05'; +ERROR: role "alter_role_2" does not exist +-- make sure that CURRENT_USER just works fine +ALTER ROLE CURRENT_USER WITH CONNECTION LIMIT 123; +SELECT rolconnlimit FROM pg_authid WHERE rolname = CURRENT_USER; + rolconnlimit +-------------- + 123 +(1 row) + +SELECT run_command_on_workers($$SELECT rolconnlimit FROM pg_authid WHERE rolname = CURRENT_USER;$$); + run_command_on_workers +------------------------- + (localhost,57637,t,123) + (localhost,57638,t,123) +(2 rows) + +-- make sure that SESSION_USER just works fine +ALTER ROLE SESSION_USER WITH CONNECTION LIMIT 124; +SELECT rolconnlimit FROM pg_authid WHERE rolname = SESSION_USER; + rolconnlimit +-------------- + 124 +(1 row) + +SELECT run_command_on_workers($$SELECT rolconnlimit FROM pg_authid WHERE rolname = SESSION_USER;$$); + run_command_on_workers +------------------------- + (localhost,57637,t,124) + (localhost,57638,t,124) +(2 rows) + +-- now lets test the passwords in more detail +ALTER ROLE alter_role_1 WITH PASSWORD NULL; +SELECT rolpassword is NULL FROM pg_authid WHERE rolname = 'alter_role_1'; + ?column? +---------- + t +(1 row) + +SELECT run_command_on_workers($$SELECT rolpassword is NULL FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +------------------------ + (localhost,57637,t,t) + (localhost,57638,t,t) +(2 rows) + +ALTER ROLE alter_role_1 WITH PASSWORD 'test1'; +SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'; + rolpassword +------------------------------------- + md52f9cc8d65e37edcc45c4a489bdfc699d +(1 row) + +SELECT run_command_on_workers($$SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +--------------------------------------------------------- + (localhost,57637,t,md52f9cc8d65e37edcc45c4a489bdfc699d) + (localhost,57638,t,md52f9cc8d65e37edcc45c4a489bdfc699d) +(2 rows) + +ALTER ROLE alter_role_1 WITH ENCRYPTED PASSWORD 'test2'; +SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'; + rolpassword +------------------------------------- + md5e17f7818c5ec023fa87bdb97fd3e842e +(1 row) + +SELECT run_command_on_workers($$SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +--------------------------------------------------------- + (localhost,57637,t,md5e17f7818c5ec023fa87bdb97fd3e842e) + (localhost,57638,t,md5e17f7818c5ec023fa87bdb97fd3e842e) +(2 rows) + +ALTER ROLE alter_role_1 WITH ENCRYPTED PASSWORD 'md59cce240038b7b335c6aa9674a6f13e72'; +SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'; + rolpassword +------------------------------------- + md59cce240038b7b335c6aa9674a6f13e72 +(1 row) + +SELECT run_command_on_workers($$SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +--------------------------------------------------------- + (localhost,57637,t,md59cce240038b7b335c6aa9674a6f13e72) + (localhost,57638,t,md59cce240038b7b335c6aa9674a6f13e72) +(2 rows) + +-- edge case role names +CREATE ROLE "alter_role'1" WITH LOGIN; +NOTICE: not propagating CREATE ROLE/USER commands to worker nodes +HINT: Connect to worker nodes directly to manually create all necessary users and roles. +SELECT run_command_on_workers($$CREATE ROLE "alter_role'1" WITH LOGIN;$$); + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE ROLE") + (localhost,57638,t,"CREATE ROLE") +(2 rows) + +ALTER ROLE "alter_role'1" CREATEROLE; +SELECT rolcreaterole FROM pg_authid WHERE rolname = 'alter_role''1'; + rolcreaterole +--------------- + t +(1 row) + +SELECT run_command_on_workers($$SELECT rolcreaterole FROM pg_authid WHERE rolname = 'alter_role''1'$$); + run_command_on_workers +------------------------ + (localhost,57637,t,t) + (localhost,57638,t,t) +(2 rows) + +CREATE ROLE "alter_role""1" WITH LOGIN; +NOTICE: not propagating CREATE ROLE/USER commands to worker nodes +HINT: Connect to worker nodes directly to manually create all necessary users and roles. +SELECT run_command_on_workers($$CREATE ROLE "alter_role""1" WITH LOGIN;$$); + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE ROLE") + (localhost,57638,t,"CREATE ROLE") +(2 rows) + +ALTER ROLE "alter_role""1" CREATEROLE; +SELECT rolcreaterole FROM pg_authid WHERE rolname = 'alter_role"1'; + rolcreaterole +--------------- + t +(1 row) + +SELECT run_command_on_workers($$SELECT rolcreaterole FROM pg_authid WHERE rolname = 'alter_role"1'$$); + run_command_on_workers +------------------------ + (localhost,57637,t,t) + (localhost,57638,t,t) +(2 rows) + +-- add node +ALTER ROLE alter_role_1 WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 66 VALID UNTIL '2032-05-05' PASSWORD 'test3'; +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; + row +-------------------------------------------------------------------------- + (alter_role_1,t,t,t,t,t,t,t,66,md5ead5c53df946838b1291bba7757f41a7,2032) +(1 row) + +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +------------------------------------------------------------------------------------------------ + (localhost,57637,t,"(alter_role_1,t,t,t,t,t,t,t,66,md5ead5c53df946838b1291bba7757f41a7,2032)") + (localhost,57638,t,"(alter_role_1,t,t,t,t,t,t,t,66,md5ead5c53df946838b1291bba7757f41a7,2032)") +(2 rows) + +SELECT master_remove_node('localhost', :worker_1_port); + master_remove_node +-------------------- + +(1 row) + +ALTER ROLE alter_role_1 WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 0 VALID UNTIL '2052-05-05' PASSWORD 'test4'; +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; + row +------------------------------------------------------------------------- + (alter_role_1,f,f,f,f,f,f,f,0,md5be308f25c7b1a2d50c85cf7e6f074df9,2052) +(1 row) + +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +----------------------------------------------------------------------------------------------- + (localhost,57638,t,"(alter_role_1,f,f,f,f,f,f,f,0,md5be308f25c7b1a2d50c85cf7e6f074df9,2052)") +(1 row) + +SELECT 1 FROM master_add_node('localhost', :worker_1_port); + ?column? +---------- + 1 +(1 row) + +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; + row +------------------------------------------------------------------------- + (alter_role_1,f,f,f,f,f,f,f,0,md5be308f25c7b1a2d50c85cf7e6f074df9,2052) +(1 row) + +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); + run_command_on_workers +----------------------------------------------------------------------------------------------- + (localhost,57637,t,"(alter_role_1,f,f,f,f,f,f,f,0,md5be308f25c7b1a2d50c85cf7e6f074df9,2052)") + (localhost,57638,t,"(alter_role_1,f,f,f,f,f,f,f,0,md5be308f25c7b1a2d50c85cf7e6f074df9,2052)") +(2 rows) + +-- table belongs to a role +-- we don't support propagation of configuration_parameters and notice the users +ALTER ROLE alter_role_1 SET enable_hashagg TO FALSE; +NOTICE: Citus partially supports ALTER ROLE for distributed databases +DETAIL: Citus does not propagate ALTER ROLE ... SET/RESET commands to workers +HINT: You can manually alter roles on workers. +-- we don't support propagation of ALTER ROLE ... RENAME TO commands. +ALTER ROLE alter_role_1 RENAME TO alter_role_1_new; +NOTICE: MD5 password cleared because of role rename +NOTICE: Citus partially supports ALTER ROLE for distributed databases +DETAIL: Citus does not propagate ALTER ROLE ... RENAME TO commands to workers +HINT: You can manually alter roles on workers. +SET citus.enable_alter_role_propagation to OFF; +DROP SCHEMA alter_role CASCADE; diff --git a/src/test/regress/expected/isolation_alter_role_propagation.out b/src/test/regress/expected/isolation_alter_role_propagation.out new file mode 100644 index 000000000..777a95273 --- /dev/null +++ b/src/test/regress/expected/isolation_alter_role_propagation.out @@ -0,0 +1,123 @@ +Parsed test spec with 2 sessions + +starting permutation: s1-enable-propagation s2-enable-propagation s1-begin s1-alter-role-1 s2-add-node s1-commit +run_command_on_workers + +(localhost,57637,t,"CREATE ROLE") +(localhost,57638,t,"CREATE ROLE") +step s1-enable-propagation: + SET citus.enable_alter_role_propagation to ON; + +step s2-enable-propagation: + SET citus.enable_alter_role_propagation to ON; + +step s1-begin: + BEGIN; + +step s1-alter-role-1: + ALTER ROLE alter_role_1 NOSUPERUSER; + +step s2-add-node: + SELECT 1 FROM master_add_node('localhost', 57637); + +step s1-commit: + COMMIT; + +step s2-add-node: <... completed> +?column? + +1 +run_command_on_workers + +(localhost,57637,t,"DROP ROLE") +(localhost,57638,t,"DROP ROLE") + +starting permutation: s1-enable-propagation s2-enable-propagation s1-begin s1-add-node s2-alter-role-1 s1-commit +run_command_on_workers + +(localhost,57637,t,"CREATE ROLE") +(localhost,57638,t,"CREATE ROLE") +step s1-enable-propagation: + SET citus.enable_alter_role_propagation to ON; + +step s2-enable-propagation: + SET citus.enable_alter_role_propagation to ON; + +step s1-begin: + BEGIN; + +step s1-add-node: + SELECT 1 FROM master_add_node('localhost', 57637); + +?column? + +1 +step s2-alter-role-1: + ALTER ROLE alter_role_1 NOSUPERUSER; + +step s1-commit: + COMMIT; + +step s2-alter-role-1: <... completed> +run_command_on_workers + +(localhost,57637,t,"DROP ROLE") +(localhost,57638,t,"DROP ROLE") + +starting permutation: s1-enable-propagation s2-enable-propagation s1-begin s1-alter-role-1 s2-alter-role-1 s1-commit +run_command_on_workers + +(localhost,57637,t,"CREATE ROLE") +(localhost,57638,t,"CREATE ROLE") +step s1-enable-propagation: + SET citus.enable_alter_role_propagation to ON; + +step s2-enable-propagation: + SET citus.enable_alter_role_propagation to ON; + +step s1-begin: + BEGIN; + +step s1-alter-role-1: + ALTER ROLE alter_role_1 NOSUPERUSER; + +step s2-alter-role-1: + ALTER ROLE alter_role_1 NOSUPERUSER; + +step s1-commit: + COMMIT; + +step s2-alter-role-1: <... completed> +error in steps s1-commit s2-alter-role-1: ERROR: tuple concurrently updated +run_command_on_workers + +(localhost,57637,t,"DROP ROLE") +(localhost,57638,t,"DROP ROLE") + +starting permutation: s1-enable-propagation s2-enable-propagation s1-begin s1-alter-role-1 s2-alter-role-2 s1-commit +run_command_on_workers + +(localhost,57637,t,"CREATE ROLE") +(localhost,57638,t,"CREATE ROLE") +step s1-enable-propagation: + SET citus.enable_alter_role_propagation to ON; + +step s2-enable-propagation: + SET citus.enable_alter_role_propagation to ON; + +step s1-begin: + BEGIN; + +step s1-alter-role-1: + ALTER ROLE alter_role_1 NOSUPERUSER; + +step s2-alter-role-2: + ALTER ROLE alter_role_2 NOSUPERUSER; + +step s1-commit: + COMMIT; + +run_command_on_workers + +(localhost,57637,t,"DROP ROLE") +(localhost,57638,t,"DROP ROLE") diff --git a/src/test/regress/isolation_schedule b/src/test/regress/isolation_schedule index c16e633c5..58d39b259 100644 --- a/src/test/regress/isolation_schedule +++ b/src/test/regress/isolation_schedule @@ -27,6 +27,7 @@ test: isolation_drop_shards isolation_copy_placement_vs_modification test: isolation_insert_vs_vacuum isolation_transaction_recovery test: isolation_progress_monitoring test: isolation_dump_local_wait_edges +test: isolation_alter_role_propagation test: isolation_replace_wait_function test: isolation_distributed_deadlock_detection diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index 0e481cd1c..1e484c89a 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -18,6 +18,7 @@ test: multi_extension test: multi_703_upgrade test: multi_cluster_management +test: alter_role_propagation test: multi_test_helpers test: multi_table_ddl test: multi_name_lengths diff --git a/src/test/regress/specs/isolation_alter_role_propagation.spec b/src/test/regress/specs/isolation_alter_role_propagation.spec new file mode 100644 index 000000000..efbba51fa --- /dev/null +++ b/src/test/regress/specs/isolation_alter_role_propagation.spec @@ -0,0 +1,75 @@ +setup +{ + CREATE ROLE alter_role_1 WITH LOGIN; + SELECT run_command_on_workers($$CREATE ROLE alter_role_1 WITH LOGIN;$$); + + CREATE ROLE alter_role_2 WITH LOGIN; + SELECT run_command_on_workers($$CREATE ROLE alter_role_2 WITH LOGIN;$$); +} + +teardown +{ + DROP ROLE IF EXISTS alter_role_1; + SELECT run_command_on_workers($$DROP ROLE IF EXISTS alter_role_1;$$); + + DROP ROLE IF EXISTS alter_role_2; + SELECT run_command_on_workers($$DROP ROLE IF EXISTS alter_role_2;$$); +} + +session "s1" + +step "s1-enable-propagation" +{ + SET citus.enable_alter_role_propagation to ON; +} + +step "s1-begin" +{ + BEGIN; +} + +step "s1-alter-role-1" +{ + ALTER ROLE alter_role_1 NOSUPERUSER; +} + +step "s1-add-node" +{ + SELECT 1 FROM master_add_node('localhost', 57637); +} + +step "s1-commit" +{ + COMMIT; +} + + +session "s2" + +step "s2-enable-propagation" +{ + SET citus.enable_alter_role_propagation to ON; +} + +step "s2-alter-role-1" +{ + ALTER ROLE alter_role_1 NOSUPERUSER; +} + +step "s2-alter-role-2" +{ + ALTER ROLE alter_role_2 NOSUPERUSER; +} + +step "s2-add-node" +{ + SELECT 1 FROM master_add_node('localhost', 57637); +} + +permutation "s1-enable-propagation" "s2-enable-propagation" "s1-begin" "s1-alter-role-1" "s2-add-node" "s1-commit" +permutation "s1-enable-propagation" "s2-enable-propagation" "s1-begin" "s1-add-node" "s2-alter-role-1" "s1-commit" + +permutation "s1-enable-propagation" "s2-enable-propagation" "s1-begin" "s1-alter-role-1" "s2-alter-role-1" "s1-commit" +permutation "s1-enable-propagation" "s2-enable-propagation" "s1-begin" "s1-alter-role-1" "s2-alter-role-2" "s1-commit" + + diff --git a/src/test/regress/sql/alter_role_propagation.sql b/src/test/regress/sql/alter_role_propagation.sql new file mode 100644 index 000000000..35cf4707f --- /dev/null +++ b/src/test/regress/sql/alter_role_propagation.sql @@ -0,0 +1,92 @@ +CREATE SCHEMA alter_role; + +SET citus.enable_alter_role_propagation to ON; + +CREATE ROLE alter_role_1 WITH LOGIN; +SELECT run_command_on_workers($$CREATE ROLE alter_role_1 WITH LOGIN;$$); + +-- postgres errors out +ALTER ROLE alter_role_1 WITH SUPERUSER NOSUPERUSER; + +-- make sure that we propagate all options accurately +ALTER ROLE alter_role_1 WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 66 VALID UNTIL '2032-05-05'; +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); + +-- make sure that we propagate all options accurately +ALTER ROLE alter_role_1 WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 0 VALID UNTIL '2052-05-05'; +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); + +-- make sure that non-existent users are handled properly +ALTER ROLE alter_role_2 WITH SUPERUSER NOSUPERUSER; +ALTER ROLE alter_role_2 WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 66 VALID UNTIL '2032-05-05'; + +-- make sure that CURRENT_USER just works fine +ALTER ROLE CURRENT_USER WITH CONNECTION LIMIT 123; +SELECT rolconnlimit FROM pg_authid WHERE rolname = CURRENT_USER; +SELECT run_command_on_workers($$SELECT rolconnlimit FROM pg_authid WHERE rolname = CURRENT_USER;$$); + +-- make sure that SESSION_USER just works fine +ALTER ROLE SESSION_USER WITH CONNECTION LIMIT 124; +SELECT rolconnlimit FROM pg_authid WHERE rolname = SESSION_USER; +SELECT run_command_on_workers($$SELECT rolconnlimit FROM pg_authid WHERE rolname = SESSION_USER;$$); + +-- now lets test the passwords in more detail +ALTER ROLE alter_role_1 WITH PASSWORD NULL; +SELECT rolpassword is NULL FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT rolpassword is NULL FROM pg_authid WHERE rolname = 'alter_role_1'$$); + +ALTER ROLE alter_role_1 WITH PASSWORD 'test1'; +SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'$$); + +ALTER ROLE alter_role_1 WITH ENCRYPTED PASSWORD 'test2'; +SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'$$); + +ALTER ROLE alter_role_1 WITH ENCRYPTED PASSWORD 'md59cce240038b7b335c6aa9674a6f13e72'; +SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT rolpassword FROM pg_authid WHERE rolname = 'alter_role_1'$$); + + +-- edge case role names + +CREATE ROLE "alter_role'1" WITH LOGIN; +SELECT run_command_on_workers($$CREATE ROLE "alter_role'1" WITH LOGIN;$$); +ALTER ROLE "alter_role'1" CREATEROLE; +SELECT rolcreaterole FROM pg_authid WHERE rolname = 'alter_role''1'; +SELECT run_command_on_workers($$SELECT rolcreaterole FROM pg_authid WHERE rolname = 'alter_role''1'$$); + +CREATE ROLE "alter_role""1" WITH LOGIN; +SELECT run_command_on_workers($$CREATE ROLE "alter_role""1" WITH LOGIN;$$); +ALTER ROLE "alter_role""1" CREATEROLE; +SELECT rolcreaterole FROM pg_authid WHERE rolname = 'alter_role"1'; +SELECT run_command_on_workers($$SELECT rolcreaterole FROM pg_authid WHERE rolname = 'alter_role"1'$$); + + +-- add node +ALTER ROLE alter_role_1 WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 66 VALID UNTIL '2032-05-05' PASSWORD 'test3'; +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); +SELECT master_remove_node('localhost', :worker_1_port); +ALTER ROLE alter_role_1 WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 0 VALID UNTIL '2052-05-05' PASSWORD 'test4'; +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); +SELECT 1 FROM master_add_node('localhost', :worker_1_port); +SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'; +SELECT run_command_on_workers($$SELECT row(rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, EXTRACT (year FROM rolvaliduntil)) FROM pg_authid WHERE rolname = 'alter_role_1'$$); + + +-- table belongs to a role + + +-- we don't support propagation of configuration_parameters and notice the users +ALTER ROLE alter_role_1 SET enable_hashagg TO FALSE; + +-- we don't support propagation of ALTER ROLE ... RENAME TO commands. +ALTER ROLE alter_role_1 RENAME TO alter_role_1_new; + +SET citus.enable_alter_role_propagation to OFF; + +DROP SCHEMA alter_role CASCADE; \ No newline at end of file