Greetings, The attached patch for review implements a few additional role attributes (all requested by users or clients in various forums) for common operations which currently require superuser privileges. This is not a complete solution for all of the superuser-only privileges we have but it's certainly good progress and along the correct path, as shown below in a review of the existing superuser checks in the backend.
First though, the new privileges, about which the bikeshedding can begin, short-and-sweet format: BACKUP: pg_start_backup() pg_stop_backup() pg_switch_xlog() pg_create_restore_point() LOGROTATE: pg_rotate_logfile() MONITOR: View detailed information regarding other processes. pg_stat_get_wal_senders() pg_stat_get_activity() PROCSIGNAL: pg_signal_backend() (not allowed to signal superuser-owned backends) Before you ask, yes, this patch also does a bit of rework and cleanup. Yes, that could be done as an independent patch- just ask, but the changes are relatively minor. One curious item to note is that the current if(!superuser()) {} block approach has masked an inconsistency in at least the FDW code which required a change to the regression tests- namely, we normally force FDW owners to have USAGE rights on the FDW, but that was being bypassed when a superuser makes the call. I seriously doubt that was intentional. I won't push back if someone really wants it to stay that way though. This also includes the previously discussed changes for pgstat and friends to use role membership instead of GetUserId() == backend_role. Full documentation is not included but will be forthcoming, of course. As part of working through what the best solution is regarding the existing superuser-only checks, I did a review of more-or-less all of the ones which exist in the backend. From that review, I came to the conclusion (certainly one which can be debated, but still) that most cases of superuser() checks that should be possible for non-superusers to do are yes/no privileges, except for when it comes to server-side COPY and CREATE TABLESPACE operations, which need a form of directory-level access control also (patch for that will be showing up shortly..). For posterity's sake, here's my review and comments on the various existing superuser checks in the backend (those not addressed above): CREATE EXTENSION This could be a role attribute as the others above, but I didn't want to try and include it in this patch as it has a lot of hairy parts, I expect. Connect using 'reserved' slots This is another one which we might add as another role attribute. Only needed during recovery, where it's fine to require superuser: pg_xlog_replay_pause() pg_xlog_replay_resume() Directory / File access (addressed independently): libpq/be/fsstubs.c lo_import() lo_export() commands/tablespace.c - create tablespace commands/copy.c - server-side COPY TO/FROM utils/adt/genfile.c pg_read_file() / pg_read_text_file() / pg_read_binary_file() pg_stat_file() pg_ls_dir() Lots of things depend on being able to create C functions. These, in my view, should either be done through extensions or should remain the purview of the superuser. Below are the ones which I identified as falling into this category: commands/tsearchcmd.c Create text search parser Create text search template tcop/utility.c LOAD (load shared library) commands/foreigncmds.c Create FDW, Alter FDW FDW ownership commands/functioncmds.c create binary-compatible casts create untrusted-language functions command/proclang.c Create procedural languages (unless PL exists in pg_pltemplate) Create custom procedural language commands/opclasscmds.c Create operator class Create operator family Alter operator family commands/aggregatecmds.c Create aggregates which use 'internal' types commands/functioncmds.c create leakproof functions alter function to be leakproof command/event_trigger.c create event trigger Other cases which I don't think would make sense to change (and some I wonder if we should prevent the superuser from doing, unless they have rolcatupdate or similar...): commands/alter.c rename objects (for objects which don't have an 'owner') change object schema (ditto) commands/trigger.c enable or disable internal triggers commands/functioncmds.c execute DO blocks with untrusted languages commands/dbcommands.c allow encoding/locale to be different commands/user.c set 'superuser' on a role alter superuser roles (other options) drop superuser roles alter role membership for superusers force 'granted by' on a role grant alter global settings rename superuser roles utils/misc/guc.c set superuser-only GUCs use ALTER SYSTEM show ALL GUCs get superuser-only GUC info define custom placeholder GUC utils/adt/misc.c reload database configuration utils/init/postinit.c connect in binary upgrade mode connect while database is shutting down Another discussion could be had regarding the superuser-only GUCs. Thanks! Stephen
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c new file mode 100644 index 133143d..fe7eed9 *** a/src/backend/access/transam/xlogfuncs.c --- b/src/backend/access/transam/xlogfuncs.c *************** *** 27,32 **** --- 27,33 ---- #include "miscadmin.h" #include "replication/walreceiver.h" #include "storage/smgr.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/numeric.h" #include "utils/guc.h" *************** pg_start_backup(PG_FUNCTION_ARGS) *** 54,63 **** backupidstr = text_to_cstring(backupid); ! if (!superuser() && !has_rolreplication(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser or replication role to run a backup"))); startpoint = do_pg_start_backup(backupidstr, fast, NULL, NULL); --- 55,65 ---- backupidstr = text_to_cstring(backupid); ! if (!has_replication_privilege(GetUserId()) ! && !has_backup_privilege(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser, replication role or backup role to run a backup"))); startpoint = do_pg_start_backup(backupidstr, fast, NULL, NULL); *************** pg_stop_backup(PG_FUNCTION_ARGS) *** 82,91 **** { XLogRecPtr stoppoint; ! if (!superuser() && !has_rolreplication(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! (errmsg("must be superuser or replication role to run a backup")))); stoppoint = do_pg_stop_backup(NULL, true, NULL); --- 84,94 ---- { XLogRecPtr stoppoint; ! if (!has_replication_privilege(GetUserId()) ! && !has_backup_privilege(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser, replication role or backup role to run a backup"))); stoppoint = do_pg_stop_backup(NULL, true, NULL); *************** pg_switch_xlog(PG_FUNCTION_ARGS) *** 100,109 **** { XLogRecPtr switchpoint; ! if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! (errmsg("must be superuser to switch transaction log files")))); if (RecoveryInProgress()) ereport(ERROR, --- 103,112 ---- { XLogRecPtr switchpoint; ! if (!has_backup_privilege(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser or backup role to switch transaction log files"))); if (RecoveryInProgress()) ereport(ERROR, *************** pg_create_restore_point(PG_FUNCTION_ARGS *** 129,138 **** char *restore_name_str; XLogRecPtr restorepoint; ! if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! (errmsg("must be superuser to create a restore point")))); if (RecoveryInProgress()) ereport(ERROR, --- 132,141 ---- char *restore_name_str; XLogRecPtr restorepoint; ! if (!has_backup_privilege(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! (errmsg("must be superuser or backup role to create a restore point")))); if (RecoveryInProgress()) ereport(ERROR, diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c new file mode 100644 index d30612c..64cf9f6 *** a/src/backend/catalog/aclchk.c --- b/src/backend/catalog/aclchk.c *************** static AclMode restrict_and_check_grant( *** 143,148 **** --- 143,149 ---- AttrNumber att_number, const char *colname); static AclMode pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum, Oid roleid, AclMode mask, AclMaskHow how); + static bool has_catupdate_privilege(Oid roleid); #ifdef ACLDEBUG *************** aclcheck_error_type(AclResult aclerr, Oi *** 3425,3431 **** /* Check if given user has rolcatupdate privilege according to pg_authid */ static bool ! has_rolcatupdate(Oid roleid) { bool rolcatupdate; HeapTuple tuple; --- 3426,3432 ---- /* Check if given user has rolcatupdate privilege according to pg_authid */ static bool ! has_catupdate_privilege(Oid roleid) { bool rolcatupdate; HeapTuple tuple; *************** pg_class_aclmask(Oid table_oid, Oid role *** 3630,3636 **** if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) && IsSystemClass(table_oid, classForm) && classForm->relkind != RELKIND_VIEW && ! !has_rolcatupdate(roleid) && !allowSystemTableMods) { #ifdef ACLDEBUG --- 3631,3637 ---- if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) && IsSystemClass(table_oid, classForm) && classForm->relkind != RELKIND_VIEW && ! !has_catupdate_privilege(roleid) && !allowSystemTableMods) { #ifdef ACLDEBUG *************** has_createrole_privilege(Oid roleid) *** 5080,5085 **** --- 5081,5111 ---- return result; } + /* + * Check whether specified role has REPLICATION privilege (or is a superuser) + */ + bool + has_replication_privilege(Oid roleid) + { + bool result = false; + HeapTuple utup; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication; + ReleaseSysCache(utup); + } + return result; + } + + /* + * Check whether specified role has BYPASSRLS privilege (or is a superuser) + */ bool has_bypassrls_privilege(Oid roleid) { *************** has_bypassrls_privilege(Oid roleid) *** 5097,5102 **** --- 5123,5217 ---- ReleaseSysCache(utup); } return result; + } + + /* + * Check whether specified role has BACKUP privilege (or is a superuser) + */ + bool + has_backup_privilege(Oid roleid) + { + bool result = false; + HeapTuple utup; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rolbackup; + ReleaseSysCache(utup); + } + + return result; + } + + /* + * Check whether specified role has LOGROTATE privilege (or is a superuser) + */ + bool + has_logrotate_privilege(Oid roleid) + { + bool result = false; + HeapTuple utup; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rollogrotate; + ReleaseSysCache(utup); + } + return result; + } + + /* + * Check whether specified role has MONITOR privilege (or is a superuser) + */ + bool + has_monitor_privilege(Oid roleid) + { + bool result = false; + HeapTuple utup; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rolmonitor; + ReleaseSysCache(utup); + } + return result; + } + + /* + * Check whether specified role has PROCSIGNAL privilege (or is a superuser) + */ + bool + has_procsignal_privilege(Oid roleid) + { + bool result = false; + HeapTuple utup; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rolprocsignal; + ReleaseSysCache(utup); + } + return result; } /* diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c new file mode 100644 index c9a9baf..ed89b23 *** a/src/backend/commands/alter.c --- b/src/backend/commands/alter.c *************** AlterObjectOwner_internal(Relation rel, *** 807,852 **** bool *nulls; bool *replaces; ! /* Superusers can bypass permission checks */ ! if (!superuser()) { ! AclObjectKind aclkind = get_object_aclkind(classId); ! /* must be owner */ ! if (!has_privs_of_role(GetUserId(), old_ownerId)) { ! char *objname; ! char namebuf[NAMEDATALEN]; ! ! if (Anum_name != InvalidAttrNumber) ! { ! datum = heap_getattr(oldtup, Anum_name, ! RelationGetDescr(rel), &isnull); ! Assert(!isnull); ! objname = NameStr(*DatumGetName(datum)); ! } ! else ! { ! snprintf(namebuf, sizeof(namebuf), "%u", ! HeapTupleGetOid(oldtup)); ! objname = namebuf; ! } ! aclcheck_error(ACLCHECK_NOT_OWNER, aclkind, objname); } ! /* Must be able to become new owner */ ! check_is_member_of_role(GetUserId(), new_ownerId); ! ! /* New owner must have CREATE privilege on namespace */ ! if (OidIsValid(namespaceId)) { ! AclResult aclresult; ! ! aclresult = pg_namespace_aclcheck(namespaceId, new_ownerId, ! ACL_CREATE); ! if (aclresult != ACLCHECK_OK) ! aclcheck_error(aclresult, ACL_KIND_NAMESPACE, ! get_namespace_name(namespaceId)); } } /* Build a modified tuple */ --- 807,848 ---- bool *nulls; bool *replaces; ! AclObjectKind aclkind = get_object_aclkind(classId); ! ! /* must be owner */ ! if (!has_privs_of_role(GetUserId(), old_ownerId)) { ! char *objname; ! char namebuf[NAMEDATALEN]; ! if (Anum_name != InvalidAttrNumber) { ! datum = heap_getattr(oldtup, Anum_name, ! RelationGetDescr(rel), &isnull); ! Assert(!isnull); ! objname = NameStr(*DatumGetName(datum)); } ! else { ! snprintf(namebuf, sizeof(namebuf), "%u", ! HeapTupleGetOid(oldtup)); ! objname = namebuf; } + aclcheck_error(ACLCHECK_NOT_OWNER, aclkind, objname); + } + /* Must be able to become new owner */ + check_is_member_of_role(GetUserId(), new_ownerId); + + /* New owner must have CREATE privilege on namespace */ + if (OidIsValid(namespaceId)) + { + AclResult aclresult; + + aclresult = pg_namespace_aclcheck(namespaceId, new_ownerId, + ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_NAMESPACE, + get_namespace_name(namespaceId)); } /* Build a modified tuple */ diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c new file mode 100644 index ab4ed6c..aad6ae4 *** a/src/backend/commands/foreigncmds.c --- b/src/backend/commands/foreigncmds.c *************** AlterForeignServerOwner_internal(Relatio *** 332,361 **** if (form->srvowner != newOwnerId) { ! /* Superusers can always do it */ ! if (!superuser()) ! { ! Oid srvId; ! AclResult aclresult; ! srvId = HeapTupleGetOid(tup); ! /* Must be owner */ ! if (!pg_foreign_server_ownercheck(srvId, GetUserId())) ! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, ! NameStr(form->srvname)); ! /* Must be able to become new owner */ ! check_is_member_of_role(GetUserId(), newOwnerId); ! /* New owner must have USAGE privilege on foreign-data wrapper */ ! aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE); ! if (aclresult != ACLCHECK_OK) ! { ! ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw); ! aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname); ! } } form->srvowner = newOwnerId; --- 332,359 ---- if (form->srvowner != newOwnerId) { ! Oid srvId; ! AclResult aclresult; ! srvId = HeapTupleGetOid(tup); ! /* Must be owner */ ! if (!pg_foreign_server_ownercheck(srvId, GetUserId())) ! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, ! NameStr(form->srvname)); ! /* Must be able to become new owner */ ! check_is_member_of_role(GetUserId(), newOwnerId); ! /* New owner must have USAGE privilege on foreign-data wrapper */ ! aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ! ACL_USAGE); ! if (aclresult != ACLCHECK_OK) ! { ! ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw); ! ! aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname); } form->srvowner = newOwnerId; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c new file mode 100644 index ecdff1e..5fb470f *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** ATExecChangeOwner(Oid relationOid, Oid n *** 8625,8651 **** /* skip permission checks when recursing to index or toast table */ if (!recursing) { ! /* Superusers can always do it */ ! if (!superuser()) ! { ! Oid namespaceOid = tuple_class->relnamespace; ! AclResult aclresult; ! /* Otherwise, must be owner of the existing object */ ! if (!pg_class_ownercheck(relationOid, GetUserId())) ! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, ! RelationGetRelationName(target_rel)); ! /* Must be able to become new owner */ ! check_is_member_of_role(GetUserId(), newOwnerId); ! /* New owner must have CREATE privilege on namespace */ ! aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ! ACL_CREATE); ! if (aclresult != ACLCHECK_OK) ! aclcheck_error(aclresult, ACL_KIND_NAMESPACE, ! get_namespace_name(namespaceOid)); ! } } memset(repl_null, false, sizeof(repl_null)); --- 8625,8647 ---- /* skip permission checks when recursing to index or toast table */ if (!recursing) { ! Oid namespaceOid = tuple_class->relnamespace; ! AclResult aclresult; ! /* Must be owner of the existing object */ ! if (!pg_class_ownercheck(relationOid, GetUserId())) ! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, ! RelationGetRelationName(target_rel)); ! /* Must be able to become new owner */ ! check_is_member_of_role(GetUserId(), newOwnerId); ! /* New owner must have CREATE privilege on namespace */ ! aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ! ACL_CREATE); ! if (aclresult != ACLCHECK_OK) ! aclcheck_error(aclresult, ACL_KIND_NAMESPACE, ! get_namespace_name(namespaceOid)); } memset(repl_null, false, sizeof(repl_null)); diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c new file mode 100644 index 55a6881..c25e237 *** a/src/backend/commands/typecmds.c --- b/src/backend/commands/typecmds.c *************** AlterTypeOwner(List *names, Oid newOwner *** 3302,3325 **** */ if (typTup->typowner != newOwnerId) { ! /* Superusers can always do it */ ! if (!superuser()) ! { ! /* Otherwise, must be owner of the existing object */ ! if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId())) ! aclcheck_error_type(ACLCHECK_NOT_OWNER, HeapTupleGetOid(tup)); ! /* Must be able to become new owner */ ! check_is_member_of_role(GetUserId(), newOwnerId); ! /* New owner must have CREATE privilege on namespace */ ! aclresult = pg_namespace_aclcheck(typTup->typnamespace, ! newOwnerId, ! ACL_CREATE); ! if (aclresult != ACLCHECK_OK) ! aclcheck_error(aclresult, ACL_KIND_NAMESPACE, ! get_namespace_name(typTup->typnamespace)); ! } /* * If it's a composite type, invoke ATExecChangeOwner so that we fix --- 3302,3321 ---- */ if (typTup->typowner != newOwnerId) { ! /* Must be owner of the existing object */ ! if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId())) ! aclcheck_error_type(ACLCHECK_NOT_OWNER, HeapTupleGetOid(tup)); ! /* Must be able to become new owner */ ! check_is_member_of_role(GetUserId(), newOwnerId); ! /* New owner must have CREATE privilege on namespace */ ! aclresult = pg_namespace_aclcheck(typTup->typnamespace, ! newOwnerId, ! ACL_CREATE); ! if (aclresult != ACLCHECK_OK) ! aclcheck_error(aclresult, ACL_KIND_NAMESPACE, ! get_namespace_name(typTup->typnamespace)); /* * If it's a composite type, invoke ATExecChangeOwner so that we fix diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c new file mode 100644 index 1a73fd8..6e4cd8b *** a/src/backend/commands/user.c --- b/src/backend/commands/user.c *************** static void DelRoleMems(const char *role *** 55,69 **** List *memberNames, List *memberIds, bool admin_opt); - - /* Check if current user has createrole privileges */ - static bool - have_createrole_privilege(void) - { - return has_createrole_privilege(GetUserId()); - } - - /* * CREATE ROLE */ --- 55,60 ---- *************** CreateRole(CreateRoleStmt *stmt) *** 88,93 **** --- 79,88 ---- bool canlogin = false; /* Can this user login? */ bool isreplication = false; /* Is this a replication role? */ bool bypassrls = false; /* Is this a row security enabled role? */ + bool backup = false; + bool logrotate = false; + bool monitor = false; + bool procsignal = false; int connlimit = -1; /* maximum connections allowed */ List *addroleto = NIL; /* roles to make this a member of */ List *rolemembers = NIL; /* roles to be members of this role */ *************** CreateRole(CreateRoleStmt *stmt) *** 108,113 **** --- 103,112 ---- DefElem *dadminmembers = NULL; DefElem *dvalidUntil = NULL; DefElem *dbypassRLS = NULL; + DefElem *dbackup = NULL; + DefElem *dlogrotate = NULL; + DefElem *dmonitor = NULL; + DefElem *dprocsignal = NULL; /* The defaults can vary depending on the original statement type */ switch (stmt->stmt_type) *************** CreateRole(CreateRoleStmt *stmt) *** 242,247 **** --- 241,278 ---- errmsg("conflicting or redundant options"))); dbypassRLS = defel; } + else if (strcmp(defel->defname, "backup") == 0) + { + if (dbackup) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dbackup = defel; + } + else if (strcmp(defel->defname, "logrotate") == 0) + { + if (dlogrotate) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dlogrotate = defel; + } + else if (strcmp(defel->defname, "monitor") == 0) + { + if (dmonitor) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dmonitor = defel; + } + else if (strcmp(defel->defname, "procsignal") == 0) + { + if (dprocsignal) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dprocsignal = defel; + } else elog(ERROR, "option \"%s\" not recognized", defel->defname); *************** CreateRole(CreateRoleStmt *stmt) *** 279,284 **** --- 310,323 ---- validUntil = strVal(dvalidUntil->arg); if (dbypassRLS) bypassrls = intVal(dbypassRLS->arg) != 0; + if (dbackup) + backup = intVal(dbackup->arg) != 0; + if (dlogrotate) + logrotate = intVal(dlogrotate->arg) != 0; + if (dmonitor) + monitor = intVal(dmonitor->arg) != 0; + if (dprocsignal) + procsignal = intVal(dprocsignal->arg) != 0; /* Check some permissions first */ if (issuper) *************** CreateRole(CreateRoleStmt *stmt) *** 304,310 **** } else { ! if (!have_createrole_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create role"))); --- 343,349 ---- } else { ! if (!has_createrole_privilege(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create role"))); *************** CreateRole(CreateRoleStmt *stmt) *** 395,400 **** --- 434,443 ---- new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null; new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls); + new_record[Anum_pg_authid_rolbackup - 1] = BoolGetDatum(backup); + new_record[Anum_pg_authid_rollogrotate - 1] = BoolGetDatum(logrotate); + new_record[Anum_pg_authid_rolmonitor - 1] = BoolGetDatum(monitor); + new_record[Anum_pg_authid_rolprocsignal - 1] = BoolGetDatum(procsignal); tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls); *************** AlterRole(AlterRoleStmt *stmt) *** 496,501 **** --- 539,548 ---- Datum validUntil_datum; /* same, as timestamptz Datum */ bool validUntil_null; bool bypassrls = -1; + bool backup = -1; + bool logrotate = -1; + bool monitor = -1; + bool procsignal = -1; DefElem *dpassword = NULL; DefElem *dissuper = NULL; DefElem *dinherit = NULL; *************** AlterRole(AlterRoleStmt *stmt) *** 507,512 **** --- 554,563 ---- DefElem *drolemembers = NULL; DefElem *dvalidUntil = NULL; DefElem *dbypassRLS = NULL; + DefElem *dbackup = NULL; + DefElem *dlogrotate = NULL; + DefElem *dmonitor = NULL; + DefElem *dprocsignal = NULL; Oid roleid; /* Extract options from the statement node tree */ *************** AlterRole(AlterRoleStmt *stmt) *** 609,614 **** --- 660,697 ---- errmsg("conflicting or redundant options"))); dbypassRLS = defel; } + else if (strcmp(defel->defname, "backup") == 0) + { + if (dbackup) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dbackup = defel; + } + else if (strcmp(defel->defname, "logrotate") == 0) + { + if (dlogrotate) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dlogrotate = defel; + } + else if (strcmp(defel->defname, "monitor") == 0) + { + if (dmonitor) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dmonitor = defel; + } + else if (strcmp(defel->defname, "procsignal") == 0) + { + if (dprocsignal) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dprocsignal = defel; + } else elog(ERROR, "option \"%s\" not recognized", defel->defname); *************** AlterRole(AlterRoleStmt *stmt) *** 642,647 **** --- 725,738 ---- validUntil = strVal(dvalidUntil->arg); if (dbypassRLS) bypassrls = intVal(dbypassRLS->arg); + if (dbackup) + backup = intVal(dbackup->arg); + if (dlogrotate) + logrotate = intVal(dlogrotate->arg); + if (dmonitor) + monitor = intVal(dmonitor->arg); + if (dprocsignal) + procsignal = intVal(dprocsignal->arg); /* * Scan the pg_authid relation to be certain the user exists. *************** AlterRole(AlterRoleStmt *stmt) *** 682,694 **** (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to change bypassrls attribute"))); } ! else if (!have_createrole_privilege()) { if (!(inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 && !dconnlimit && !rolemembers && !validUntil && --- 773,789 ---- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to change bypassrls attribute"))); } ! else if (!has_createrole_privilege(GetUserId())) { if (!(inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 && + backup < 0 && + logrotate < 0 && + monitor < 0 && + procsignal < 0 && !dconnlimit && !rolemembers && !validUntil && *************** AlterRole(AlterRoleStmt *stmt) *** 821,826 **** --- 916,945 ---- new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true; } + if (backup >= 0) + { + new_record[Anum_pg_authid_rolbackup - 1] = BoolGetDatum(backup > 0); + new_record_repl[Anum_pg_authid_rolbackup - 1] = true; + } + + if (logrotate >= 0) + { + new_record[Anum_pg_authid_rollogrotate - 1] = BoolGetDatum(logrotate > 0); + new_record_repl[Anum_pg_authid_rollogrotate - 1] = true; + } + + if (monitor >= 0) + { + new_record[Anum_pg_authid_rolmonitor - 1] = BoolGetDatum(monitor > 0); + new_record_repl[Anum_pg_authid_rolmonitor - 1] = true; + } + + if (procsignal >= 0) + { + new_record[Anum_pg_authid_rolprocsignal - 1] = BoolGetDatum(procsignal > 0); + new_record_repl[Anum_pg_authid_rolprocsignal - 1] = true; + } + new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record, new_record_nulls, new_record_repl); simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple); *************** AlterRoleSet(AlterRoleSetStmt *stmt) *** 898,904 **** } else { ! if (!have_createrole_privilege() && HeapTupleGetOid(roletuple) != GetUserId()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), --- 1017,1023 ---- } else { ! if (!has_createrole_privilege(GetUserId()) && HeapTupleGetOid(roletuple) != GetUserId()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), *************** DropRole(DropRoleStmt *stmt) *** 951,957 **** pg_auth_members_rel; ListCell *item; ! if (!have_createrole_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to drop role"))); --- 1070,1076 ---- pg_auth_members_rel; ListCell *item; ! if (!has_createrole_privilege(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to drop role"))); *************** RenameRole(const char *oldname, const ch *** 1182,1188 **** } else { ! if (!have_createrole_privilege()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to rename role"))); --- 1301,1307 ---- } else { ! if (!has_createrole_privilege(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to rename role"))); *************** AddRoleMems(const char *rolename, Oid ro *** 1409,1415 **** } else { ! if (!have_createrole_privilege() && !is_admin_of_role(grantorId, roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), --- 1528,1534 ---- } else { ! if (!has_createrole_privilege(GetUserId()) && !is_admin_of_role(grantorId, roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), *************** DelRoleMems(const char *rolename, Oid ro *** 1555,1561 **** } else { ! if (!have_createrole_privilege() && !is_admin_of_role(GetUserId(), roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), --- 1674,1680 ---- } else { ! if (!has_createrole_privilege(GetUserId()) && !is_admin_of_role(GetUserId(), roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y new file mode 100644 index c98c27a..f23a5f3 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** AlterOptRoleElem: *** 977,982 **** --- 977,998 ---- */ $$ = makeDefElem("inherit", (Node *)makeInteger(FALSE)); } + else if (strcmp($1, "backup") == 0) + $$ = makeDefElem("backup", (Node *)makeInteger(TRUE)); + else if (strcmp($1, "nobackup") == 0) + $$ = makeDefElem("backup", (Node *)makeInteger(FALSE)); + else if (strcmp($1, "logrotate") == 0) + $$ = makeDefElem("logrotate", (Node *)makeInteger(TRUE)); + else if (strcmp($1, "nologrotate") == 0) + $$ = makeDefElem("logrotate", (Node *)makeInteger(FALSE)); + else if (strcmp($1, "monitor") == 0) + $$ = makeDefElem("monitor", (Node *)makeInteger(TRUE)); + else if (strcmp($1, "nomonitor") == 0) + $$ = makeDefElem("monitor", (Node *)makeInteger(FALSE)); + else if (strcmp($1, "procsignal") == 0) + $$ = makeDefElem("procsignal", (Node *)makeInteger(TRUE)); + else if (strcmp($1, "noprocsignal") == 0) + $$ = makeDefElem("procsignal", (Node *)makeInteger(FALSE)); else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c new file mode 100644 index 3a5ec2f..a7417c9 *** a/src/backend/replication/logical/logicalfuncs.c --- b/src/backend/replication/logical/logicalfuncs.c *************** *** 27,32 **** --- 27,33 ---- #include "mb/pg_wchar.h" + #include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/inval.h" *************** XLogRead(char *buf, TimeLineID tli, XLog *** 200,214 **** } } - static void - check_permissions(void) - { - if (!superuser() && !has_rolreplication(GetUserId())) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser or replication role to use replication slots")))); - } - /* * read_page callback for logical decoding contexts. * --- 201,206 ---- *************** pg_logical_slot_get_changes_guts(Functio *** 322,328 **** if (get_call_result_type(fcinfo, NULL, &p->tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); ! check_permissions(); CheckLogicalDecodingRequirements(); --- 314,323 ---- if (get_call_result_type(fcinfo, NULL, &p->tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); ! if (!has_replication_privilege(GetUserId())) ! ereport(ERROR, ! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser or replication role to use replication slots"))); CheckLogicalDecodingRequirements(); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c new file mode 100644 index bd4701f..ebae1a2 *** a/src/backend/replication/slotfuncs.c --- b/src/backend/replication/slotfuncs.c *************** *** 20,37 **** #include "replication/slot.h" #include "replication/logical.h" #include "replication/logicalfuncs.h" #include "utils/builtins.h" #include "utils/pg_lsn.h" - static void - check_permissions(void) - { - if (!superuser() && !has_rolreplication(GetUserId())) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser or replication role to use replication slots")))); - } - /* * SQL function for creating a new physical (streaming replication) * replication slot. --- 20,29 ---- #include "replication/slot.h" #include "replication/logical.h" #include "replication/logicalfuncs.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/pg_lsn.h" /* * SQL function for creating a new physical (streaming replication) * replication slot. *************** pg_create_physical_replication_slot(PG_F *** 51,57 **** if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); ! check_permissions(); CheckSlotRequirements(); --- 43,52 ---- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); ! if (!has_replication_privilege(GetUserId())) ! ereport(ERROR, ! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser or replication role to use replication slots"))); CheckSlotRequirements(); *************** pg_create_logical_replication_slot(PG_FU *** 94,100 **** if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); ! check_permissions(); CheckLogicalDecodingRequirements(); --- 89,98 ---- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); ! if (!has_replication_privilege(GetUserId())) ! ereport(ERROR, ! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser or replication role to use replication slots"))); CheckLogicalDecodingRequirements(); *************** pg_drop_replication_slot(PG_FUNCTION_ARG *** 143,149 **** { Name name = PG_GETARG_NAME(0); ! check_permissions(); CheckSlotRequirements(); --- 141,150 ---- { Name name = PG_GETARG_NAME(0); ! if (!has_replication_privilege(GetUserId())) ! ereport(ERROR, ! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser or replication role to use replication slots"))); CheckSlotRequirements(); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c new file mode 100644 index 384c9b6..456bf46 *** a/src/backend/replication/walsender.c --- b/src/backend/replication/walsender.c *************** *** 72,77 **** --- 72,78 ---- #include "storage/proc.h" #include "storage/procarray.h" #include "tcop/tcopprot.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/memutils.h" *************** pg_stat_get_wal_senders(PG_FUNCTION_ARGS *** 2832,2842 **** memset(nulls, 0, sizeof(nulls)); values[0] = Int32GetDatum(walsnd->pid); ! if (!superuser()) { /* ! * Only superusers can see details. Other users only get the pid ! * value to know it's a walsender, but no details. */ MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1); } --- 2833,2844 ---- memset(nulls, 0, sizeof(nulls)); values[0] = Int32GetDatum(walsnd->pid); ! if (!has_monitor_privilege(GetUserId())) { /* ! * Only users with the MONITOR attribute or superuser privileges can ! * see details. Other users only get the pid value to know it's a ! * walsender, but no details. */ MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1); } diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c new file mode 100644 index dc6eb2c..4f9ffe5 *** a/src/backend/utils/adt/acl.c --- b/src/backend/utils/adt/acl.c *************** static AclMode convert_role_priv_string( *** 117,122 **** --- 117,123 ---- static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode); static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue); + static bool has_inherit_privilege(Oid roleid); /* *************** RoleMembershipCacheCallback(Datum arg, i *** 4636,4642 **** /* Check if specified role has rolinherit set */ static bool ! has_rolinherit(Oid roleid) { bool result = false; HeapTuple utup; --- 4637,4643 ---- /* Check if specified role has rolinherit set */ static bool ! has_inherit_privilege(Oid roleid) { bool result = false; HeapTuple utup; *************** roles_has_privs_of(Oid roleid) *** 4697,4703 **** int i; /* Ignore non-inheriting roles */ ! if (!has_rolinherit(memberid)) continue; /* Find roles that memberid is directly a member of */ --- 4698,4704 ---- int i; /* Ignore non-inheriting roles */ ! if (!has_inherit_privilege(memberid)) continue; /* Find roles that memberid is directly a member of */ diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c new file mode 100644 index 67539ec..0515b8f *** a/src/backend/utils/adt/misc.c --- b/src/backend/utils/adt/misc.c *************** *** 37,42 **** --- 37,43 ---- #include "utils/lsyscache.h" #include "utils/ruleutils.h" #include "tcop/tcopprot.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/timestamp.h" *************** pg_signal_backend(int pid, int sig) *** 113,119 **** return SIGNAL_BACKEND_ERROR; } ! if (!(superuser() || proc->roleId == GetUserId())) return SIGNAL_BACKEND_NOPERMISSION; /* --- 114,132 ---- return SIGNAL_BACKEND_ERROR; } ! /* ! * If the current user is not a superuser, then they aren't allowed to ! * signal backends which are owned by a superuser. ! */ ! if (!superuser() && superuser_arg(proc->roleId)) ! return SIGNAL_BACKEND_NOPERMISSION; ! ! /* ! * If the current user is not a member of the role owning the process and ! * does not have the PROCSIGNAL permission, then permission is denied. ! */ ! if (!has_privs_of_role(GetUserId(), proc->roleId) ! && !has_procsignal_privilege(GetUserId())) return SIGNAL_BACKEND_NOPERMISSION; /* *************** pg_reload_conf(PG_FUNCTION_ARGS) *** 202,211 **** Datum pg_rotate_logfile(PG_FUNCTION_ARGS) { ! if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! (errmsg("must be superuser to rotate log files")))); if (!Logging_collector) { --- 215,224 ---- Datum pg_rotate_logfile(PG_FUNCTION_ARGS) { ! if (!has_logrotate_privilege(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser or have logrotate permission to rotate log files"))); if (!Logging_collector) { diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c new file mode 100644 index 44ccd37..c960ebb *** a/src/backend/utils/adt/pgstatfuncs.c --- b/src/backend/utils/adt/pgstatfuncs.c *************** *** 20,25 **** --- 20,26 ---- #include "libpq/ip.h" #include "miscadmin.h" #include "pgstat.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/inet.h" #include "utils/timestamp.h" *************** pg_stat_get_activity(PG_FUNCTION_ARGS) *** 625,630 **** --- 626,632 ---- HeapTuple tuple; LocalPgBackendStatus *local_beentry; PgBackendStatus *beentry; + Oid current_user_id; MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); *************** pg_stat_get_activity(PG_FUNCTION_ARGS) *** 674,681 **** else nulls[15] = true; ! /* Values only available to same user or superuser */ ! if (superuser() || beentry->st_userid == GetUserId()) { SockAddr zero_clientaddr; --- 676,689 ---- else nulls[15] = true; ! /* ! * Values only available to roles which are members of this role, ! * or which have the MONITOR privilege. ! */ ! current_user_id = GetUserId(); ! ! if (has_monitor_privilege(current_user_id) ! || has_privs_of_role(current_user_id, beentry->st_userid)) { SockAddr zero_clientaddr; *************** pg_stat_get_backend_activity(PG_FUNCTION *** 874,883 **** int32 beid = PG_GETARG_INT32(0); PgBackendStatus *beentry; const char *activity; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) activity = "<backend information not available>"; ! else if (!superuser() && beentry->st_userid != GetUserId()) activity = "<insufficient privilege>"; else if (*(beentry->st_activity) == '\0') activity = "<command string not enabled>"; --- 882,895 ---- int32 beid = PG_GETARG_INT32(0); PgBackendStatus *beentry; const char *activity; + Oid current_user_id; + + current_user_id = GetUserId(); if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) activity = "<backend information not available>"; ! else if (!has_monitor_privilege(current_user_id) ! && !has_privs_of_role(current_user_id, beentry->st_userid)) activity = "<insufficient privilege>"; else if (*(beentry->st_activity) == '\0') activity = "<command string not enabled>"; *************** pg_stat_get_backend_waiting(PG_FUNCTION_ *** 894,904 **** int32 beid = PG_GETARG_INT32(0); bool result; PgBackendStatus *beentry; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! if (!superuser() && beentry->st_userid != GetUserId()) PG_RETURN_NULL(); result = beentry->st_waiting; --- 906,920 ---- int32 beid = PG_GETARG_INT32(0); bool result; PgBackendStatus *beentry; + Oid current_user_id; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! current_user_id = GetUserId(); ! ! if (!has_monitor_privilege(current_user_id) ! && !has_privs_of_role(current_user_id, beentry->st_userid)) PG_RETURN_NULL(); result = beentry->st_waiting; *************** pg_stat_get_backend_activity_start(PG_FU *** 913,923 **** int32 beid = PG_GETARG_INT32(0); TimestampTz result; PgBackendStatus *beentry; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! if (!superuser() && beentry->st_userid != GetUserId()) PG_RETURN_NULL(); result = beentry->st_activity_start_timestamp; --- 929,943 ---- int32 beid = PG_GETARG_INT32(0); TimestampTz result; PgBackendStatus *beentry; + Oid current_user_id; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! current_user_id = GetUserId(); ! ! if (!has_monitor_privilege(current_user_id) ! && !has_privs_of_role(current_user_id, beentry->st_userid)) PG_RETURN_NULL(); result = beentry->st_activity_start_timestamp; *************** pg_stat_get_backend_xact_start(PG_FUNCTI *** 939,949 **** int32 beid = PG_GETARG_INT32(0); TimestampTz result; PgBackendStatus *beentry; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! if (!superuser() && beentry->st_userid != GetUserId()) PG_RETURN_NULL(); result = beentry->st_xact_start_timestamp; --- 959,973 ---- int32 beid = PG_GETARG_INT32(0); TimestampTz result; PgBackendStatus *beentry; + Oid current_user_id; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! current_user_id = GetUserId(); ! ! if (!has_monitor_privilege(current_user_id) ! && !has_privs_of_role(current_user_id, beentry->st_userid)) PG_RETURN_NULL(); result = beentry->st_xact_start_timestamp; *************** pg_stat_get_backend_start(PG_FUNCTION_AR *** 961,971 **** int32 beid = PG_GETARG_INT32(0); TimestampTz result; PgBackendStatus *beentry; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! if (!superuser() && beentry->st_userid != GetUserId()) PG_RETURN_NULL(); result = beentry->st_proc_start_timestamp; --- 985,999 ---- int32 beid = PG_GETARG_INT32(0); TimestampTz result; PgBackendStatus *beentry; + Oid current_user_id; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! current_user_id = GetUserId(); ! ! if (!has_monitor_privilege(current_user_id) ! && !has_privs_of_role(current_user_id, beentry->st_userid)) PG_RETURN_NULL(); result = beentry->st_proc_start_timestamp; *************** pg_stat_get_backend_client_addr(PG_FUNCT *** 985,995 **** SockAddr zero_clientaddr; char remote_host[NI_MAXHOST]; int ret; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! if (!superuser() && beentry->st_userid != GetUserId()) PG_RETURN_NULL(); /* A zeroed client addr means we don't know */ --- 1013,1027 ---- SockAddr zero_clientaddr; char remote_host[NI_MAXHOST]; int ret; + Oid current_user_id; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! current_user_id = GetUserId(); ! ! if (!has_monitor_privilege(current_user_id) ! && !has_privs_of_role(current_user_id, beentry->st_userid)) PG_RETURN_NULL(); /* A zeroed client addr means we don't know */ *************** pg_stat_get_backend_client_port(PG_FUNCT *** 1032,1042 **** SockAddr zero_clientaddr; char remote_port[NI_MAXSERV]; int ret; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! if (!superuser() && beentry->st_userid != GetUserId()) PG_RETURN_NULL(); /* A zeroed client addr means we don't know */ --- 1064,1082 ---- SockAddr zero_clientaddr; char remote_port[NI_MAXSERV]; int ret; + Oid current_user_id; if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL) PG_RETURN_NULL(); ! /* ! * User must have MONITOR attribute, be superuser or be the same ! * backend user. ! */ ! current_user_id = GetUserId(); ! ! if (!has_monitor_privilege(current_user_id) ! && !has_privs_of_role(current_user_id, beentry->st_userid)) PG_RETURN_NULL(); /* A zeroed client addr means we don't know */ diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c new file mode 100644 index a703c67..2e41dc5 *** a/src/backend/utils/init/miscinit.c --- b/src/backend/utils/init/miscinit.c *************** SetUserIdAndContext(Oid userid, bool sec *** 317,341 **** SecurityRestrictionContext &= ~SECURITY_LOCAL_USERID_CHANGE; } - - /* - * Check whether specified role has explicit REPLICATION privilege - */ - bool - has_rolreplication(Oid roleid) - { - bool result = false; - HeapTuple utup; - - utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); - if (HeapTupleIsValid(utup)) - { - result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication; - ReleaseSysCache(utup); - } - return result; - } - /* * Initialize user identity during normal backend startup */ --- 317,322 ---- diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c new file mode 100644 index 6a6a445..28c25f4 *** a/src/backend/utils/init/postinit.c --- b/src/backend/utils/init/postinit.c *************** InitPostgres(const char *in_dbname, Oid *** 761,767 **** { Assert(!bootstrap); ! if (!superuser() && !has_rolreplication(GetUserId())) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser or replication role to start walsender"))); --- 761,767 ---- { Assert(!bootstrap); ! if (!has_replication_privilege(GetUserId())) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser or replication role to start walsender"))); diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h new file mode 100644 index 3b63d2b..b636925 *** a/src/include/catalog/pg_authid.h --- b/src/include/catalog/pg_authid.h *************** CATALOG(pg_authid,1260) BKI_SHARED_RELAT *** 53,58 **** --- 53,62 ---- bool rolcanlogin; /* allowed to log in as session user? */ bool rolreplication; /* role used for streaming replication */ bool rolbypassrls; /* allowed to bypass row level security? */ + bool rolbackup; /* allowed to peform backup operations? */ + bool rollogrotate; /* allowed to rotate log files? */ + bool rolmonitor; /* allowed to view pg_stat_* details? */ + bool rolprocsignal; /* allowed to signal backed processes? */ int32 rolconnlimit; /* max connections allowed (-1=no limit) */ /* remaining fields may be null; use heap_getattr to read them! */ *************** typedef FormData_pg_authid *Form_pg_auth *** 74,80 **** * compiler constants for pg_authid * ---------------- */ ! #define Natts_pg_authid 12 #define Anum_pg_authid_rolname 1 #define Anum_pg_authid_rolsuper 2 #define Anum_pg_authid_rolinherit 3 --- 78,84 ---- * compiler constants for pg_authid * ---------------- */ ! #define Natts_pg_authid 16 #define Anum_pg_authid_rolname 1 #define Anum_pg_authid_rolsuper 2 #define Anum_pg_authid_rolinherit 3 *************** typedef FormData_pg_authid *Form_pg_auth *** 84,92 **** #define Anum_pg_authid_rolcanlogin 7 #define Anum_pg_authid_rolreplication 8 #define Anum_pg_authid_rolbypassrls 9 ! #define Anum_pg_authid_rolconnlimit 10 ! #define Anum_pg_authid_rolpassword 11 ! #define Anum_pg_authid_rolvaliduntil 12 /* ---------------- * initial contents of pg_authid --- 88,100 ---- #define Anum_pg_authid_rolcanlogin 7 #define Anum_pg_authid_rolreplication 8 #define Anum_pg_authid_rolbypassrls 9 ! #define Anum_pg_authid_rolbackup 10 ! #define Anum_pg_authid_rollogrotate 11 ! #define Anum_pg_authid_rolmonitor 12 ! #define Anum_pg_authid_rolprocsignal 13 ! #define Anum_pg_authid_rolconnlimit 14 ! #define Anum_pg_authid_rolpassword 15 ! #define Anum_pg_authid_rolvaliduntil 16 /* ---------------- * initial contents of pg_authid *************** typedef FormData_pg_authid *Form_pg_auth *** 95,101 **** * user choices. * ---------------- */ ! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_)); #define BOOTSTRAP_SUPERUSERID 10 --- 103,109 ---- * user choices. * ---------------- */ ! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t t t t t -1 _null_ _null_)); #define BOOTSTRAP_SUPERUSERID 10 diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h new file mode 100644 index 2ba9885..011bd62 *** a/src/include/miscadmin.h --- b/src/include/miscadmin.h *************** extern void ValidatePgVersion(const char *** 434,440 **** extern void process_shared_preload_libraries(void); extern void process_session_preload_libraries(void); extern void pg_bindtextdomain(const char *domain); - extern bool has_rolreplication(Oid roleid); /* in access/transam/xlog.c */ extern bool BackupInProgress(void); --- 434,439 ---- diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h new file mode 100644 index a8e3164..ac242b2 *** a/src/include/utils/acl.h --- b/src/include/utils/acl.h *************** extern bool pg_event_trigger_ownercheck( *** 328,332 **** --- 328,337 ---- extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid); extern bool has_createrole_privilege(Oid roleid); extern bool has_bypassrls_privilege(Oid roleid); + extern bool has_replication_privilege(Oid roleid); + extern bool has_backup_privilege(Oid roleid); + extern bool has_logrotate_privilege(Oid roleid); + extern bool has_monitor_privilege(Oid roleid); + extern bool has_procsignal_privilege(Oid roleid); #endif /* ACL_H */ diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out new file mode 100644 index e4dedb0..b3b5cd0 *** a/src/test/regress/expected/foreign_data.out --- b/src/test/regress/expected/foreign_data.out *************** ERROR: must be owner of foreign server *** 394,399 **** --- 394,400 ---- ALTER SERVER s1 OWNER TO regress_test_role; -- ERROR ERROR: must be owner of foreign server s1 RESET ROLE; + GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role; ALTER SERVER s1 OWNER TO regress_test_role; GRANT regress_test_role2 TO regress_test_role; SET ROLE regress_test_role; *************** GRANT USAGE ON FOREIGN DATA WRAPPER foo *** 417,422 **** --- 418,424 ---- SET ROLE regress_test_role; ALTER SERVER s1 OWNER TO regress_test_indirect; RESET ROLE; + REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM regress_test_role; DROP ROLE regress_test_indirect; -- ERROR ERROR: role "regress_test_indirect" cannot be dropped because some objects depend on it DETAIL: owner of server s1 diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql new file mode 100644 index de9dbc8..91d51c9 *** a/src/test/regress/sql/foreign_data.sql --- b/src/test/regress/sql/foreign_data.sql *************** SET ROLE regress_test_role; *** 164,169 **** --- 164,170 ---- ALTER SERVER s1 VERSION '1.1'; -- ERROR ALTER SERVER s1 OWNER TO regress_test_role; -- ERROR RESET ROLE; + GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role; ALTER SERVER s1 OWNER TO regress_test_role; GRANT regress_test_role2 TO regress_test_role; SET ROLE regress_test_role; *************** GRANT USAGE ON FOREIGN DATA WRAPPER foo *** 183,188 **** --- 184,190 ---- SET ROLE regress_test_role; ALTER SERVER s1 OWNER TO regress_test_indirect; RESET ROLE; + REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM regress_test_role; DROP ROLE regress_test_indirect; -- ERROR \des+
signature.asc
Description: Digital signature