almost-super-user problems that we haven't fixed yet
Due to cf5eb37c5ee0cc54c80d95c1695d7fca1f7c68cb,
e5b8a4c098ad6add39626a14475148872cd687e0, and prior commits touching
related code, it should now be possible to consider handing out
CREATEROLE as a reasonable alternative to handing out SUPERUSER. Prior
to cf5eb37c5ee0cc54c80d95c1695d7fca1f7c68cb, giving CREATEROLE meant
giving away control of pg_execute_server_programs and every other
built-in role, so it wasn't possible to give CREATEROLE to a user who
isn't completely trusted. Now, that should be OK. CREATEROLE users
will only gain control over roles they create (and any others that the
superuser grants to them). Furthermore, if you set
createrole_self_grant to 'inherit' or 'set, inherit', a CREATEROLE
user will automatically inherit the privileges of the users they
create, hopefully making them feel like they are almost a superuser
without letting them actually take over the world.
Not very surprisingly, those commits failed to solve every single
problem that anyone has ever thought about in this area.
Here is a probably-incomplete list of related problems that are so far unsolved:
1. It's still possible for a CREATEROLE user to hand out role
attributes that they don't possess. The new prohibitions in
cf5eb37c5ee0cc54c80d95c1695d7fca1f7c68cb prevent a CREATEROLE user
from handing out membership in a role on which they lack sufficient
permissions, but they don't prevent a CREATEROLE user who lacks
CREATEDB from creating a new user who does have CREATEDB. I think we
should subject the CREATEDB, REPLICATION, and BYPASSRLS attributes to
the same rule that we now use for role memberships: you've got to have
the property in order to give it to someone else. In the case of
CREATEDB, this would tighten the current rules, which allow you to
give out CREATEDB without having it. In the case of REPLICATION and
BYPASSRLS, this would liberalize the current rules: right now, a
CREATEROLE user cannot give REPLICATION or BYPASSRLS to another user
even if they possess those attributes.
This proposal doesn't address the CREATEROLE or CONNECTION LIMIT
properties. It seems possible to me that someone might want to set up
a CREATEROLE user who can't make more such users, and this proposal
doesn't manufacture any way of doing that. It also doesn't let you
constraint the ability of a CREATEROLE user to set a CONNECTION LIMIT
for some other user. I think that's OK. It might be nice to have ways
of imposing such restrictions at some point in the future, but it is
not very obvious what to do about such cases and, importantly, I don't
think there's any security impact from failing to address those cases.
If a CREATEROLE user without CREATEDB can create a new role that does
have CREATEDB, that's a privilege escalation. If they can hand out
CREATEROLE, that isn't: they already have it.
2. It's still impossible for a CREATEROLE user to execute CREATE
SUBSCRIPTION, so they can't get logical replication working. There was
a previous thread about fixing this at
/messages/by-id/9DFC88D3-1300-4DE8-ACBC-4CEF84399A53@enterprisedb.com
and the corresponding CF entry is listed as committed, but
CreateSubscription() still requires superuser, so I think that maybe
that thread only got some of the preliminary permissions-check work
committed and the core problem is yet to be solved.
3. Only superusers can control event triggers. In the thread at
/messages/by-id/914FF898-5AC4-4E02-8A05-3876087007FB@enterprisedb.com
it was proposed, based on an idea from Tom, to allow any user to
create event triggers but, approximately, to only have them fire for
code running as a user whose privileges the creator already has. I
don't recall the precise rule that was proposed and it might need
rethinking in view of 3d14e171e9e2236139e8976f3309a588bcc8683b, and I
think there was also some opposition to that proposal, so I'm not sure
what the way forward here is.
4. You can reserve a small number of connections for the superuser
with superuser_reserved_connections, but there's no way to do a
similar thing for any other user. As mentioned above, a CREATEROLE
user could set connection limits for every created role such that the
sum of those limits is less than max_connections by some margin, but
that restricts each of those roles individually, not all of them in
the aggregate. Maybe we could address this by inventing a new GUC
reserved_connections and a predefined role
pg_use_reserved_connections.
5. If you set createrole_self_grant = 'set, inherit' and make alice a
CREATEROLE user and she goes around and creates a bunch of other users
and they all run around and create a bunch of objects and then alice
tries to pg_dump the entire database, it will work ... provided that
there are no tables owned by any other user. If the superuser has
created any tables, or there's another CREATEROLE user wandering
around creating tables, or even a non-CREATEROLE user whose
permissions alice does not have, pg_dump will try to lock them and
die. I don't see any perfect solution to this problem: we can neither
let alice dump objects on which she does not have permission, nor can
we silently skip them in the interest of giving alice a better user
experience, because if we do that then somebody will end up with a
partial database backup that they think is a complete database backup
and that will be a really bad day. However, I think we could add a
pg_dump option that says, hey, please only try to dump tables we have
permission to dump, and skip the others. Or, of course, alice could
use -T and -N as required, but a dedicated switch for
skip-stuff-i-can't-access-quietly might be a better user experience. I
guess you could also argue that this isn't really a problem in the
first place because you could always choose to grant
pg_read_all_tables to the almost-super-user, but maybe that's not
always desirable. Not sure.
Just to be clear, there are lots of other things that a non-superuser
cannot do, such as CREATE LANGUAGE. However, I'm excluding that kind
of thing from this list because it's intrinsically unsafe to allow a
non-superuser to do that, since it's probably a gateway to arbitrary
code execution and then you can probably get superuser for real, and
control of the OS account, too. What I'm interested in is developing a
list of things that could, with the right infrastructure, be delegated
to non-superusers safely but which, as things stand today, cannot be
delegated to non-superusers. Contributions to the list are most
welcome as are thoughts on the proposals above.
Thanks,
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Jan 16, 2023 at 02:29:56PM -0500, Robert Haas wrote:
4. You can reserve a small number of connections for the superuser
with superuser_reserved_connections, but there's no way to do a
similar thing for any other user. As mentioned above, a CREATEROLE
user could set connection limits for every created role such that the
sum of those limits is less than max_connections by some margin, but
that restricts each of those roles individually, not all of them in
the aggregate. Maybe we could address this by inventing a new GUC
reserved_connections and a predefined role
pg_use_reserved_connections.
I've written something like this before, and I'd be happy to put together a
patch if there is interest.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Jan 16, 2023 at 5:37 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Mon, Jan 16, 2023 at 02:29:56PM -0500, Robert Haas wrote:
4. You can reserve a small number of connections for the superuser
with superuser_reserved_connections, but there's no way to do a
similar thing for any other user. As mentioned above, a CREATEROLE
user could set connection limits for every created role such that the
sum of those limits is less than max_connections by some margin, but
that restricts each of those roles individually, not all of them in
the aggregate. Maybe we could address this by inventing a new GUC
reserved_connections and a predefined role
pg_use_reserved_connections.I've written something like this before, and I'd be happy to put together a
patch if there is interest.
Cool. I had been thinking of coding it up myself, but you doing it works, too.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Jan 16, 2023 at 09:06:10PM -0500, Robert Haas wrote:
On Mon, Jan 16, 2023 at 5:37 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Mon, Jan 16, 2023 at 02:29:56PM -0500, Robert Haas wrote:
4. You can reserve a small number of connections for the superuser
with superuser_reserved_connections, but there's no way to do a
similar thing for any other user. As mentioned above, a CREATEROLE
user could set connection limits for every created role such that the
sum of those limits is less than max_connections by some margin, but
that restricts each of those roles individually, not all of them in
the aggregate. Maybe we could address this by inventing a new GUC
reserved_connections and a predefined role
pg_use_reserved_connections.I've written something like this before, and I'd be happy to put together a
patch if there is interest.Cool. I had been thinking of coding it up myself, but you doing it works, too.
Alright. The one design question I have is whether this should be a new
set of reserved connections or replace superuser_reserved_connections
entirely.
If we create a new batch of reserved connections, only roles with
privileges of pg_use_reserved_connections would be able to connect if the
number of remaining slots is greater than superuser_reserved_connections
but less than or equal to superuser_reserved_connections +
reserved_connections. Only superusers would be able to connect if the
number of remaining slots is less than or equal to
superuser_reserved_connections. This helps avoid blocking new superuser
connections even if you've reserved some connections for non-superusers.
Іf we replace superuser_reserved_connections, we're basically opening up
the existing functionality to non-superusers, which is simpler and probably
more in the spirit of this thread, but it doesn't provide a way to prevent
blocking new superuser connections.
My preference is the former approach. This is closest to what I've written
before, and if I read your words carefully, it seems to be what you are
proposing. WDYT?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Jan 17, 2023 at 1:42 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
Alright. The one design question I have is whether this should be a new
set of reserved connections or replace superuser_reserved_connections
entirely.
I think it should definitely be something new, not a replacement.
If we create a new batch of reserved connections, only roles with
privileges of pg_use_reserved_connections would be able to connect if the
number of remaining slots is greater than superuser_reserved_connections
but less than or equal to superuser_reserved_connections +
reserved_connections. Only superusers would be able to connect if the
number of remaining slots is less than or equal to
superuser_reserved_connections. This helps avoid blocking new superuser
connections even if you've reserved some connections for non-superusers.
This is precisely what I had in mind.
I think the documentation will need some careful wordsmithing,
including adjustments to superuser_reserved_connections. We want to
recast superuser_reserved_connections as a final reserve to be touched
after even reserved_connections has been exhausted.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Jan 17, 2023 at 02:59:31PM -0500, Robert Haas wrote:
On Tue, Jan 17, 2023 at 1:42 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
If we create a new batch of reserved connections, only roles with
privileges of pg_use_reserved_connections would be able to connect if the
number of remaining slots is greater than superuser_reserved_connections
but less than or equal to superuser_reserved_connections +
reserved_connections. Only superusers would be able to connect if the
number of remaining slots is less than or equal to
superuser_reserved_connections. This helps avoid blocking new superuser
connections even if you've reserved some connections for non-superusers.This is precisely what I had in mind.
Great. Here is a first attempt at the patch.
I think the documentation will need some careful wordsmithing,
including adjustments to superuser_reserved_connections. We want to
recast superuser_reserved_connections as a final reserve to be touched
after even reserved_connections has been exhausted.
I tried to do this, but there is probably still room for improvement,
especially for the parts that discuss the relationship between
max_connections, superuser_reserved_connections, and reserved_connections.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v1-0001-Rename-ReservedBackends-to-SuperuserReservedBacke.patchtext/x-diff; charset=us-asciiDownload
From e153d2f22d4303d0bb5e8134391ebf1fa446172c Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 13:58:56 -0800
Subject: [PATCH v1 1/2] Rename ReservedBackends to SuperuserReservedBackends.
This is in preparation for adding a new reserved_connections GUC
that will use the ReservedBackends variable name.
---
src/backend/postmaster/postmaster.c | 18 +++++++++---------
src/backend/utils/init/postinit.c | 4 ++--
src/backend/utils/misc/guc_tables.c | 2 +-
src/include/postmaster/postmaster.h | 2 +-
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9cedc1b9f0..470704f364 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -204,15 +204,15 @@ char *Unix_socket_directories;
char *ListenAddresses;
/*
- * ReservedBackends is the number of backends reserved for superuser use.
- * This number is taken out of the pool size given by MaxConnections so
+ * SuperuserReservedBackends is the number of backends reserved for superuser
+ * use. This number is taken out of the pool size given by MaxConnections so
* number of backend slots available to non-superusers is
- * (MaxConnections - ReservedBackends). Note what this really means is
- * "if there are <= ReservedBackends connections available, only superusers
- * can make new connections" --- pre-existing superuser connections don't
- * count against the limit.
+ * (MaxConnections - SuperuserReservedBackends). Note what this really means
+ * is "if there are <= SuperuserReservedBackends connections available, only
+ * superusers can make new connections" --- pre-existing superuser connections
+ * don't count against the limit.
*/
-int ReservedBackends;
+int SuperuserReservedBackends;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +908,11 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (ReservedBackends >= MaxConnections)
+ if (SuperuserReservedBackends >= MaxConnections)
{
write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- ReservedBackends, MaxConnections);
+ SuperuserReservedBackends, MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index ae5a85ed65..6fa696fe8d 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -927,8 +927,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
* limited by max_connections or superuser_reserved_connections.
*/
if (!am_superuser && !am_walsender &&
- ReservedBackends > 0 &&
- !HaveNFreeProcs(ReservedBackends))
+ SuperuserReservedBackends > 0 &&
+ !HaveNFreeProcs(SuperuserReservedBackends))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for non-replication superuser connections")));
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..5aa2cda8f9 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2163,7 +2163,7 @@ struct config_int ConfigureNamesInt[] =
gettext_noop("Sets the number of connection slots reserved for superusers."),
NULL
},
- &ReservedBackends,
+ &SuperuserReservedBackends,
3, 0, MAX_BACKENDS,
NULL, NULL, NULL
},
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 203177e1ff..168d85a3d1 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -15,7 +15,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
-extern PGDLLIMPORT int ReservedBackends;
+extern PGDLLIMPORT int SuperuserReservedBackends;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
--
2.25.1
v1-0002-Introduce-reserved_connections-and-pg_use_reserve.patchtext/x-diff; charset=us-asciiDownload
From 2961ebbeb1e2910752fa4297f6aaa52492b30741 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 15:36:59 -0800
Subject: [PATCH v1 2/2] Introduce reserved_connections and
pg_use_reserved_connections.
This provides a way to reserve connection slots for non-superusers.
superuser_reserved_connections remains as a final reserve in case
reserved_connections has been exhausted.
---
doc/src/sgml/config.sgml | 40 +++++++++++++++++--
doc/src/sgml/user-manag.sgml | 5 +++
src/backend/postmaster/postmaster.c | 28 ++++++++-----
src/backend/utils/init/postinit.c | 16 ++++++--
src/backend/utils/misc/guc_tables.c | 11 +++++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_authid.dat | 5 +++
src/include/postmaster/postmaster.h | 1 +
8 files changed, 92 insertions(+), 15 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89d53f2a64..aa100870b9 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -725,18 +725,52 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
- connections will be accepted only for superusers, and no
- new replication connections will be accepted.
+ connections will be accepted only for superusers. The connection slots
+ reserved by this parameter are intended as final reserve for emergency
+ use after the slots reserved by
+ <xref linkend="guc-reserved-connections"/> have been exhausted.
</para>
<para>
The default value is three connections. The value must be less
- than <varname>max_connections</varname>.
+ than <varname>max_connections</varname> minus
+ <varname>reserved_connections</varname>.
This parameter can only be set at server start.
</para>
</listitem>
</varlistentry>
+ <varlistentry id="guc-reserved-connections" xreflabel="reserved_connections">
+ <term><varname>reserved_connections</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>reserved_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Determines the number of connection <quote>slots</quote> that are
+ reserved for connections by roles with privileges of the
+ <link linkend="predefined-roles-table"><literal>pg_used_reserved_connections</literal></link>
+ role. Whenever the number of free connection slots is greater than
+ <xref linkend="guc-superuser-reserved-connections"/> but less than or
+ equal to the sum of <varname>superuser_reserved_connections</varname>
+ and <varname>reserved_connections</varname>, new connections will be
+ accepted only for superusers and roles with privileges of
+ <literal>pg_use_reserved_connections</literal>. If
+ <varname>superuser_reserved_connections</varname> or fewer connection
+ slots are available, new connections will be accepted only for
+ superusers.
+ </para>
+
+ <para>
+ The default value is three connections. The value must be less than
+ <varname>max_connections</varname> minus
+ <varname>superuser_reserved_connections</varname>. This parameter can
+ only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-unix-socket-directories" xreflabel="unix_socket_directories">
<term><varname>unix_socket_directories</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 71a2d8f298..d553b1adab 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -689,6 +689,11 @@ DROP ROLE doomed_role;
and <link linkend="sql-lock"><command>LOCK TABLE</command></link> on all
relations.</entry>
</row>
+ <row>
+ <entry>pg_use_reserved_backends</entry>
+ <entry>Allow use of connection slots reserved via
+ <xref linkend="guc-reserved-connections"/>.</entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 470704f364..ac69d3fd9b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -205,14 +205,23 @@ char *ListenAddresses;
/*
* SuperuserReservedBackends is the number of backends reserved for superuser
- * use. This number is taken out of the pool size given by MaxConnections so
- * number of backend slots available to non-superusers is
- * (MaxConnections - SuperuserReservedBackends). Note what this really means
- * is "if there are <= SuperuserReservedBackends connections available, only
- * superusers can make new connections" --- pre-existing superuser connections
- * don't count against the limit.
+ * use, and ReservedBackends is the number of backends reserved for use by
+ * roles with privileges of the pg_use_reserved_backends predefined role.
+ * These are taken out of the pool of MaxConnections backend slots, so the
+ * number of backend slots available for roles that are neither superuser nor
+ * have privileges of pg_use_reserved_backends is
+ * (MaxConnections - SuperuserReservedBackends - ReservedBackends).
+ *
+ * If the number of remaining slots is less than or equal to
+ * SuperuserReservedBackends, only superusers can make new connections. If the
+ * number of remaining slots is greater than SuperuserReservedBackends but less
+ * than or equal to (SuperuserReservedBackends + ReservedBackends), only
+ * superusers and roles with privileges of pg_use_reserved_backends can make
+ * new connections. Note that pre-existing superuser and
+ * pg_use_reserved_backends connections don't count against the limits.
*/
int SuperuserReservedBackends;
+int ReservedBackends;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +917,12 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (SuperuserReservedBackends >= MaxConnections)
+ if (SuperuserReservedBackends + ReservedBackends >= MaxConnections)
{
- write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
+ write_stderr("%s: superuser_reserved_connections (%d) plus reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- SuperuserReservedBackends, MaxConnections);
+ SuperuserReservedBackends, ReservedBackends,
+ MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6fa696fe8d..4e74cdbb9e 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -922,9 +922,11 @@ InitPostgres(const char *in_dbname, Oid dboid,
}
/*
- * The last few connection slots are reserved for superusers. Replication
- * connections are drawn from slots reserved with max_wal_senders and not
- * limited by max_connections or superuser_reserved_connections.
+ * The last few connection slots are reserved for superusers and roles with
+ * privileges of pg_use_reserved_connections. Replication connections are
+ * drawn from slots reserved with max_wal_senders and are not limited by
+ * max_connections, superuser_reserved_connections, or
+ * reserved_connections.
*/
if (!am_superuser && !am_walsender &&
SuperuserReservedBackends > 0 &&
@@ -933,6 +935,14 @@ InitPostgres(const char *in_dbname, Oid dboid,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for non-replication superuser connections")));
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS) &&
+ !am_walsender &&
+ ReservedBackends > 0 &&
+ !HaveNFreeProcs(SuperuserReservedBackends + ReservedBackends))
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_backends")));
+
/* Check replication permissions needed for walsender processes. */
if (am_walsender)
{
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5aa2cda8f9..1e94e70b42 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2168,6 +2168,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"reserved_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+ gettext_noop("Sets the number of connection slots reserved for roles "
+ "with privileges of pg_use_reserved_connections."),
+ NULL
+ },
+ &ReservedBackends,
+ 3, 0, MAX_BACKENDS,
+ NULL, NULL, NULL
+ },
+
{
{"min_dynamic_shared_memory", PGC_POSTMASTER, RESOURCES_MEM,
gettext_noop("Amount of dynamic shared memory reserved at startup."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..5107afe140 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -64,6 +64,7 @@
#port = 5432 # (change requires restart)
#max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
+#reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
# (change requires restart)
#unix_socket_group = '' # (change requires restart)
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 2a2fee7d28..f2e5663c9f 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -89,5 +89,10 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
+ rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' },
]
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 168d85a3d1..18ef5afa75 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -16,6 +16,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
extern PGDLLIMPORT int SuperuserReservedBackends;
+extern PGDLLIMPORT int ReservedBackends;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
--
2.25.1
On Tue, Jan 17, 2023 at 7:15 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
Great. Here is a first attempt at the patch.
In general, looks good. I think this will often call HaveNFreeProcs
twice, though, and that would be better to avoid, e.g.
if (!am_superuser && !am_walsender && (SuperuserReservedBackends +
ReservedBackends) > 0)
&& !HaveNFreeProcs(SuperuserReservedBackends + ReservedBackends))
{
if (!HaveNFreeProcs(SuperuserReservedBackends))
remaining connection slots are reserved for non-replication
superuser connections;
if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
remaining connection slots are reserved for roles with
privileges of pg_use_reserved_backends;
}
In the common case where we hit neither limit, this only counts free
connection slots once. We could do even better by making
HaveNFreeProcs have an out parameter for the number of free procs
actually found when it returns false, but that's probably not
important.
I don't think that we should default both the existing GUC and the new
one to 3, because that raises the default limit in the case where the
new feature is not used from 3 to 6. I think we should default one of
them to 0 and the other one to 3. Not sure which one should get which
value.
I think the documentation will need some careful wordsmithing,
including adjustments to superuser_reserved_connections. We want to
recast superuser_reserved_connections as a final reserve to be touched
after even reserved_connections has been exhausted.I tried to do this, but there is probably still room for improvement,
especially for the parts that discuss the relationship between
max_connections, superuser_reserved_connections, and reserved_connections.
I think it's pretty good the way you have it. I agree that there might
be a way to make it even better, but I don't think I know what it is.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Jan 16, 2023 at 2:29 PM Robert Haas <robertmhaas@gmail.com> wrote:
1. It's still possible for a CREATEROLE user to hand out role
attributes that they don't possess. The new prohibitions in
cf5eb37c5ee0cc54c80d95c1695d7fca1f7c68cb prevent a CREATEROLE user
from handing out membership in a role on which they lack sufficient
permissions, but they don't prevent a CREATEROLE user who lacks
CREATEDB from creating a new user who does have CREATEDB. I think we
should subject the CREATEDB, REPLICATION, and BYPASSRLS attributes to
the same rule that we now use for role memberships: you've got to have
the property in order to give it to someone else. In the case of
CREATEDB, this would tighten the current rules, which allow you to
give out CREATEDB without having it. In the case of REPLICATION and
BYPASSRLS, this would liberalize the current rules: right now, a
CREATEROLE user cannot give REPLICATION or BYPASSRLS to another user
even if they possess those attributes.This proposal doesn't address the CREATEROLE or CONNECTION LIMIT
properties. It seems possible to me that someone might want to set up
a CREATEROLE user who can't make more such users, and this proposal
doesn't manufacture any way of doing that. It also doesn't let you
constraint the ability of a CREATEROLE user to set a CONNECTION LIMIT
for some other user. I think that's OK. It might be nice to have ways
of imposing such restrictions at some point in the future, but it is
not very obvious what to do about such cases and, importantly, I don't
think there's any security impact from failing to address those cases.
If a CREATEROLE user without CREATEDB can create a new role that does
have CREATEDB, that's a privilege escalation. If they can hand out
CREATEROLE, that isn't: they already have it.
Here is a patch implementing the above proposal. Since this is fairly
closely related to already-committed work, I would like to get this
into v16. That way, all the changes to how CREATEROLE works will go
into a single release, which seems less confusing for users. It is
also fairly clear to me that this is an improvement over the status
quo. Sometimes things that seem clear to me turn out to be false, so
if this change seems like a problem to you, please let me know.
Thanks,
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v1-0001-Adjust-interaction-of-CREATEROLE-with-role-proper.patchapplication/octet-stream; name=v1-0001-Adjust-interaction-of-CREATEROLE-with-role-proper.patchDownload
From e2e8b89d1b3ceda882c919b3cd68a81ccb5580f4 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 18 Jan 2023 12:04:51 -0500
Subject: [PATCH v1] Adjust interaction of CREATEROLE with role properties.
Previously, a CREATEROLE user without SUPERUSER could not alter
REPLICATION users in any way, and could not set the BYPASSRLS
attribute. However, they could manipulate the CREATEDB property
even if they themselves did not possess it.
With this change, a CREATEROLE user without SUPERUSER can set or
clear the REPLICATION, BYPASSRLS, or CREATEDB property on a new
role or a role that they have rights to manage if and only if
that property is set for their own role.
This implements the standard idea that you can't give permissions
you don't have (but you can give the ones you do have). We might
in the future want to provide more powerful ways to constrain
what a CREATEROLE user can do - for example, to limit whether
CONNECTION LIMIT can be set or the values to which it can be set -
but that is left as future work.
---
doc/src/sgml/ref/alter_role.sgml | 13 ++--
doc/src/sgml/ref/create_role.sgml | 23 ++-----
src/backend/commands/dbcommands.c | 3 +-
src/backend/commands/user.c | 82 +++++++++++------------
src/include/commands/dbcommands.h | 1 +
src/test/regress/expected/create_role.out | 53 ++++++++++++---
src/test/regress/sql/create_role.sql | 45 +++++++++++--
7 files changed, 137 insertions(+), 83 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index fbb4612e70..ff2b88e9b6 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -70,11 +70,14 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
<link linkend="sql-revoke"><command>REVOKE</command></link> for that.)
Attributes not mentioned in the command retain their previous settings.
Database superusers can change any of these settings for any role.
- Roles having <literal>CREATEROLE</literal> privilege can change any of these
- settings except <literal>SUPERUSER</literal>, <literal>REPLICATION</literal>,
- and <literal>BYPASSRLS</literal>; but only for non-superuser and
- non-replication roles for which they have been
- granted <literal>ADMIN OPTION</literal>.
+ Non-superuser roles having <literal>CREATEROLE</literal> privilege can
+ change most of these properties, but only for non-superuser and
+ non-replication roles for which they have been granted
+ <literal>ADMIN OPTION</literal>. Non-superusers cannot change the
+ <literal>SUPERUSER</literal> property and can change the
+ <literal>CREATEDB</literal>, <literal>REPLICATION</literal>, and
+ <literal>BYPASSRLS</literal> properties only if they possess the
+ corresponding property themselves.
Ordinary roles can only change their own password.
</para>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 7ce4e38b45..c101df6e2f 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -109,6 +109,8 @@ in sync when changing the above synopsis!
<literal>NOCREATEDB</literal> will deny a role the ability to
create databases. If not specified,
<literal>NOCREATEDB</literal> is the default.
+ Only superuser roles or roles with <literal>CREATEDB</literal>
+ can specify <literal>CREATEDB</literal>.
</para>
</listitem>
</varlistentry>
@@ -188,8 +190,8 @@ in sync when changing the above synopsis!
highly privileged role, and should only be used on roles actually
used for replication. If not specified,
<literal>NOREPLICATION</literal> is the default.
- You must be a superuser to create a new role having the
- <literal>REPLICATION</literal> attribute.
+ Only superuser roles or roles with <literal>REPLICATION</literal>
+ can specify <literal>REPLICATION</literal>.
</para>
</listitem>
</varlistentry>
@@ -201,8 +203,8 @@ in sync when changing the above synopsis!
<para>
These clauses determine whether a role bypasses every row-level
security (RLS) policy. <literal>NOBYPASSRLS</literal> is the default.
- You must be a superuser to create a new role having
- the <literal>BYPASSRLS</literal> attribute.
+ Only superuser roles or roles with <literal>BYPASSRLS</literal>
+ can specify <literal>BYPASSRLS</literal>.
</para>
<para>
@@ -390,19 +392,6 @@ in sync when changing the above synopsis!
specified in the SQL standard.
</para>
- <para>
- Be careful with the <literal>CREATEROLE</literal> privilege. There is no concept of
- inheritance for the privileges of a <literal>CREATEROLE</literal>-role. That
- means that even if a role does not have a certain privilege but is allowed
- to create other roles, it can easily create another role with different
- privileges than its own (except for creating roles with superuser
- privileges). For example, if the role <quote>user</quote> has the
- <literal>CREATEROLE</literal> privilege but not the <literal>CREATEDB</literal> privilege,
- nonetheless it can create a new role with the <literal>CREATEDB</literal>
- privilege. Therefore, regard roles that have the <literal>CREATEROLE</literal>
- privilege as almost-superuser-roles.
- </para>
-
<para>
<productname>PostgreSQL</productname> includes a program <xref
linkend="app-createuser"/> that has
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 518ffca09a..1f4ce2fb9c 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -121,7 +121,6 @@ static bool get_db_info(const char *name, LOCKMODE lockmode,
Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
char *dbLocProvider,
char **dbCollversion);
-static bool have_createdb_privilege(void);
static void remove_dbtablespaces(Oid db_id);
static bool check_db_file_conflict(Oid db_id);
static int errdetail_busy_db(int notherbackends, int npreparedxacts);
@@ -2742,7 +2741,7 @@ get_db_info(const char *name, LOCKMODE lockmode,
}
/* Check if current user has createdb privileges */
-static bool
+bool
have_createdb_privilege(void)
{
bool result = false;
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 4d193a6f9a..3a92e930c0 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -311,33 +311,28 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
bypassrls = boolVal(dbypassRLS->arg);
/* Check some permissions first */
- if (issuper)
+ if (!superuser_arg(currentUserId))
{
- if (!superuser())
+ if (!has_createrole_privilege(currentUserId))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create role")));
+ if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create superusers")));
- }
- else if (isreplication)
- {
- if (!superuser())
+ if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create replication users")));
- }
- else if (bypassrls)
- {
- if (!superuser())
+ errmsg("must have createdb permission to create createdb users")));
+ if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create bypassrls users")));
- }
- else
- {
- if (!have_createrole_privilege())
+ errmsg("must have replication permission to create replication users")));
+ if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("must have bypassrls to create bypassrls users")));
}
/*
@@ -748,32 +743,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
rolename = pstrdup(NameStr(authform->rolname));
roleid = authform->oid;
- /*
- * To mess with a superuser or replication role in any way you gotta be
- * superuser. We also insist on superuser to change the BYPASSRLS
- * property.
- */
- if (authform->rolsuper || dissuper)
- {
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
- }
- else if (authform->rolreplication || disreplication)
- {
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter replication roles or change replication attribute")));
- }
- else if (dbypassRLS)
- {
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to change bypassrls attribute")));
- }
+ /* To mess with a superuser in any way you gotta be superuser. */
+ if (!superuser() && (authform->rolsuper || dissuper))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to alter superuser roles or change superuser attribute")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -784,7 +758,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
/* things an unprivileged user certainly can't do */
if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
- dvalidUntil)
+ dvalidUntil || disreplication || dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied")));
@@ -795,6 +769,26 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must have CREATEROLE privilege to change another user's password")));
}
+ else if (!superuser())
+ {
+ /*
+ * Even if you have both CREATEROLE and ADMIN OPTION on a role, you
+ * can only change the CREATEDB, REPLICATION, or BYPASSRLS attributes
+ * if they are set for your own role (or you are the superuser).
+ */
+ if (dcreatedb && !have_createdb_privilege())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must have createdb privilege to change createdb attribute")));
+ if (disreplication && !has_rolreplication(currentUserId))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must have replication privilege to change replication attribute")));
+ if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ }
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 9b8818315a..5fbc3ca752 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -30,6 +30,7 @@ extern ObjectAddress AlterDatabaseOwner(const char *dbname, Oid newOwnerId);
extern Oid get_database_oid(const char *dbname, bool missing_ok);
extern char *get_database_name(Oid dbid);
+extern bool have_createdb_privilege(void);
extern void check_encoding_locale_matches(int encoding, const char *collate, const char *ctype);
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index cd49feabb3..c8beb36bab 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -2,19 +2,51 @@
CREATE ROLE regress_role_super SUPERUSER;
CREATE ROLE regress_role_admin CREATEDB CREATEROLE REPLICATION BYPASSRLS;
GRANT CREATE ON DATABASE regression TO regress_role_admin WITH GRANT OPTION;
+CREATE ROLE regress_role_limited_admin CREATEROLE;
CREATE ROLE regress_role_normal;
--- fail, only superusers can create users with these privileges
-SET SESSION AUTHORIZATION regress_role_admin;
+-- fail, CREATEROLE user can't give away role attributes without having them
+SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
ERROR: must be superuser to create superusers
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must be superuser to create replication users
+ERROR: must have replication permission to create replication users
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must be superuser to create replication users
+ERROR: must have replication permission to create replication users
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must be superuser to create bypassrls users
--- ok, having CREATEROLE is enough to create users with these privileges
+ERROR: must have bypassrls to create bypassrls users
+CREATE ROLE regress_nosuch_createdb CREATEDB;
+ERROR: must have createdb permission to create createdb users
+-- ok, can create a role without any special attributes
+CREATE ROLE regress_role_limited;
+-- fail, can't give it in any of the restricted attributes
+ALTER ROLE regress_role_limited SUPERUSER;
+ERROR: must be superuser to alter superuser roles or change superuser attribute
+ALTER ROLE regress_role_limited REPLICATION;
+ERROR: must have replication privilege to change replication attribute
+ALTER ROLE regress_role_limited CREATEDB;
+ERROR: must have createdb privilege to change createdb attribute
+ALTER ROLE regress_role_limited BYPASSRLS;
+ERROR: must have bypassrls privilege to change bypassrls attribute
+DROP ROLE regress_role_limited;
+-- ok, can give away these role attributes if you have them
+SET SESSION AUTHORIZATION regress_role_admin;
+CREATE ROLE regress_replication_bypassrls REPLICATION BYPASSRLS;
+CREATE ROLE regress_replication REPLICATION;
+CREATE ROLE regress_bypassrls BYPASSRLS;
CREATE ROLE regress_createdb CREATEDB;
+-- ok, can toggle these role attributes off and on if you have them
+ALTER ROLE regress_replication NOREPLICATION;
+ALTER ROLE regress_replication REPLICATION;
+ALTER ROLE regress_bypassrls NOBYPASSRLS;
+ALTER ROLE regress_bypassrls BYPASSRLS;
+ALTER ROLE regress_createdb NOCREATEDB;
+ALTER ROLE regress_createdb CREATEDB;
+-- fail, can't toggle SUPERUSER
+ALTER ROLE regress_createdb SUPERUSER;
+ERROR: must be superuser to alter superuser roles or change superuser attribute
+ALTER ROLE regress_createdb NOSUPERUSER;
+ERROR: must be superuser to alter superuser roles or change superuser attribute
+-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
CREATE ROLE regress_login LOGIN;
@@ -53,9 +85,9 @@ ERROR: permission denied to create database
CREATE ROLE regress_plainrole;
-- ok, roles with CREATEROLE can create new roles with it
CREATE ROLE regress_rolecreator CREATEROLE;
--- ok, roles with CREATEROLE can create new roles with privilege they lack
-CREATE ROLE regress_hasprivs CREATEDB CREATEROLE LOGIN INHERIT
- CONNECTION LIMIT 5;
+-- ok, roles with CREATEROLE can create new roles with different role
+-- attributes, including CREATEROLE
+CREATE ROLE regress_hasprivs CREATEROLE LOGIN INHERIT CONNECTION LIMIT 5;
-- ok, we should be able to modify a role we created
COMMENT ON ROLE regress_hasprivs IS 'some comment';
ALTER ROLE regress_hasprivs RENAME TO regress_tenant;
@@ -164,6 +196,9 @@ DROP ROLE regress_plainrole;
-- must revoke privileges before dropping role
REVOKE CREATE ON DATABASE regression FROM regress_createrole CASCADE;
-- ok, should be able to drop non-superuser roles we created
+DROP ROLE regress_replication_bypassrls;
+DROP ROLE regress_replication;
+DROP ROLE regress_bypassrls;
DROP ROLE regress_createdb;
DROP ROLE regress_createrole;
DROP ROLE regress_login;
diff --git a/src/test/regress/sql/create_role.sql b/src/test/regress/sql/create_role.sql
index 6b90336da2..19031b4612 100644
--- a/src/test/regress/sql/create_role.sql
+++ b/src/test/regress/sql/create_role.sql
@@ -2,17 +2,47 @@
CREATE ROLE regress_role_super SUPERUSER;
CREATE ROLE regress_role_admin CREATEDB CREATEROLE REPLICATION BYPASSRLS;
GRANT CREATE ON DATABASE regression TO regress_role_admin WITH GRANT OPTION;
+CREATE ROLE regress_role_limited_admin CREATEROLE;
CREATE ROLE regress_role_normal;
--- fail, only superusers can create users with these privileges
-SET SESSION AUTHORIZATION regress_role_admin;
+-- fail, CREATEROLE user can't give away role attributes without having them
+SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
CREATE ROLE regress_nosuch_replication REPLICATION;
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
+CREATE ROLE regress_nosuch_createdb CREATEDB;
--- ok, having CREATEROLE is enough to create users with these privileges
+-- ok, can create a role without any special attributes
+CREATE ROLE regress_role_limited;
+
+-- fail, can't give it in any of the restricted attributes
+ALTER ROLE regress_role_limited SUPERUSER;
+ALTER ROLE regress_role_limited REPLICATION;
+ALTER ROLE regress_role_limited CREATEDB;
+ALTER ROLE regress_role_limited BYPASSRLS;
+DROP ROLE regress_role_limited;
+
+-- ok, can give away these role attributes if you have them
+SET SESSION AUTHORIZATION regress_role_admin;
+CREATE ROLE regress_replication_bypassrls REPLICATION BYPASSRLS;
+CREATE ROLE regress_replication REPLICATION;
+CREATE ROLE regress_bypassrls BYPASSRLS;
CREATE ROLE regress_createdb CREATEDB;
+
+-- ok, can toggle these role attributes off and on if you have them
+ALTER ROLE regress_replication NOREPLICATION;
+ALTER ROLE regress_replication REPLICATION;
+ALTER ROLE regress_bypassrls NOBYPASSRLS;
+ALTER ROLE regress_bypassrls BYPASSRLS;
+ALTER ROLE regress_createdb NOCREATEDB;
+ALTER ROLE regress_createdb CREATEDB;
+
+-- fail, can't toggle SUPERUSER
+ALTER ROLE regress_createdb SUPERUSER;
+ALTER ROLE regress_createdb NOSUPERUSER;
+
+-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
CREATE ROLE regress_login LOGIN;
@@ -56,9 +86,9 @@ CREATE ROLE regress_plainrole;
-- ok, roles with CREATEROLE can create new roles with it
CREATE ROLE regress_rolecreator CREATEROLE;
--- ok, roles with CREATEROLE can create new roles with privilege they lack
-CREATE ROLE regress_hasprivs CREATEDB CREATEROLE LOGIN INHERIT
- CONNECTION LIMIT 5;
+-- ok, roles with CREATEROLE can create new roles with different role
+-- attributes, including CREATEROLE
+CREATE ROLE regress_hasprivs CREATEROLE LOGIN INHERIT CONNECTION LIMIT 5;
-- ok, we should be able to modify a role we created
COMMENT ON ROLE regress_hasprivs IS 'some comment';
@@ -150,6 +180,9 @@ DROP ROLE regress_plainrole;
REVOKE CREATE ON DATABASE regression FROM regress_createrole CASCADE;
-- ok, should be able to drop non-superuser roles we created
+DROP ROLE regress_replication_bypassrls;
+DROP ROLE regress_replication;
+DROP ROLE regress_bypassrls;
DROP ROLE regress_createdb;
DROP ROLE regress_createrole;
DROP ROLE regress_login;
--
2.37.1 (Apple Git-137.1)
On Wed, Jan 18, 2023 at 11:28:57AM -0500, Robert Haas wrote:
In general, looks good. I think this will often call HaveNFreeProcs
twice, though, and that would be better to avoid, e.g.
I should have thought of this. This is fixed in v2.
In the common case where we hit neither limit, this only counts free
connection slots once. We could do even better by making
HaveNFreeProcs have an out parameter for the number of free procs
actually found when it returns false, but that's probably not
important.
Actually, I think it might be important. IIUC the separate calls to
HaveNFreeProcs might return different values for the same input, which
could result in incorrect error messages (e.g., you might get the
reserved_connections message despite setting reserved_connections to 0).
So, I made this change in v2, too.
I don't think that we should default both the existing GUC and the new
one to 3, because that raises the default limit in the case where the
new feature is not used from 3 to 6. I think we should default one of
them to 0 and the other one to 3. Not sure which one should get which
value.
I chose to set reserved_connections to 0 since it is new and doesn't have a
pre-existing default value.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v2-0001-Rename-ReservedBackends-to-SuperuserReservedBacke.patchtext/x-diff; charset=us-asciiDownload
From 94ee89548fd1080447b784993a1418480f407b49 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 13:58:56 -0800
Subject: [PATCH v2 1/2] Rename ReservedBackends to SuperuserReservedBackends.
This is in preparation for adding a new reserved_connections GUC
that will use the ReservedBackends variable name.
---
src/backend/postmaster/postmaster.c | 18 +++++++++---------
src/backend/utils/init/postinit.c | 4 ++--
src/backend/utils/misc/guc_tables.c | 2 +-
src/include/postmaster/postmaster.h | 2 +-
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9cedc1b9f0..470704f364 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -204,15 +204,15 @@ char *Unix_socket_directories;
char *ListenAddresses;
/*
- * ReservedBackends is the number of backends reserved for superuser use.
- * This number is taken out of the pool size given by MaxConnections so
+ * SuperuserReservedBackends is the number of backends reserved for superuser
+ * use. This number is taken out of the pool size given by MaxConnections so
* number of backend slots available to non-superusers is
- * (MaxConnections - ReservedBackends). Note what this really means is
- * "if there are <= ReservedBackends connections available, only superusers
- * can make new connections" --- pre-existing superuser connections don't
- * count against the limit.
+ * (MaxConnections - SuperuserReservedBackends). Note what this really means
+ * is "if there are <= SuperuserReservedBackends connections available, only
+ * superusers can make new connections" --- pre-existing superuser connections
+ * don't count against the limit.
*/
-int ReservedBackends;
+int SuperuserReservedBackends;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +908,11 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (ReservedBackends >= MaxConnections)
+ if (SuperuserReservedBackends >= MaxConnections)
{
write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- ReservedBackends, MaxConnections);
+ SuperuserReservedBackends, MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index ae5a85ed65..6fa696fe8d 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -927,8 +927,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
* limited by max_connections or superuser_reserved_connections.
*/
if (!am_superuser && !am_walsender &&
- ReservedBackends > 0 &&
- !HaveNFreeProcs(ReservedBackends))
+ SuperuserReservedBackends > 0 &&
+ !HaveNFreeProcs(SuperuserReservedBackends))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for non-replication superuser connections")));
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..5aa2cda8f9 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2163,7 +2163,7 @@ struct config_int ConfigureNamesInt[] =
gettext_noop("Sets the number of connection slots reserved for superusers."),
NULL
},
- &ReservedBackends,
+ &SuperuserReservedBackends,
3, 0, MAX_BACKENDS,
NULL, NULL, NULL
},
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 203177e1ff..168d85a3d1 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -15,7 +15,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
-extern PGDLLIMPORT int ReservedBackends;
+extern PGDLLIMPORT int SuperuserReservedBackends;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
--
2.25.1
v2-0002-Introduce-reserved_connections-and-pg_use_reserve.patchtext/x-diff; charset=us-asciiDownload
From ba7e566a097fd5ffbc20f67a9caccea31ef19212 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 15:36:59 -0800
Subject: [PATCH v2 2/2] Introduce reserved_connections and
pg_use_reserved_connections.
This provides a way to reserve connection slots for non-superusers.
superuser_reserved_connections remains as a final reserve in case
reserved_connections has been exhausted.
---
doc/src/sgml/config.sgml | 40 +++++++++++++++++--
doc/src/sgml/user-manag.sgml | 5 +++
src/backend/postmaster/postmaster.c | 28 ++++++++-----
src/backend/storage/lmgr/proc.c | 13 +++---
src/backend/utils/init/postinit.c | 27 +++++++++----
src/backend/utils/misc/guc_tables.c | 11 +++++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_authid.dat | 5 +++
src/include/postmaster/postmaster.h | 1 +
src/include/storage/proc.h | 2 +-
10 files changed, 107 insertions(+), 26 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89d53f2a64..626faa4d08 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -725,18 +725,52 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
- connections will be accepted only for superusers, and no
- new replication connections will be accepted.
+ connections will be accepted only for superusers. The connection slots
+ reserved by this parameter are intended as final reserve for emergency
+ use after the slots reserved by
+ <xref linkend="guc-reserved-connections"/> have been exhausted.
</para>
<para>
The default value is three connections. The value must be less
- than <varname>max_connections</varname>.
+ than <varname>max_connections</varname> minus
+ <varname>reserved_connections</varname>.
This parameter can only be set at server start.
</para>
</listitem>
</varlistentry>
+ <varlistentry id="guc-reserved-connections" xreflabel="reserved_connections">
+ <term><varname>reserved_connections</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>reserved_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Determines the number of connection <quote>slots</quote> that are
+ reserved for connections by roles with privileges of the
+ <link linkend="predefined-roles-table"><literal>pg_used_reserved_connections</literal></link>
+ role. Whenever the number of free connection slots is greater than
+ <xref linkend="guc-superuser-reserved-connections"/> but less than or
+ equal to the sum of <varname>superuser_reserved_connections</varname>
+ and <varname>reserved_connections</varname>, new connections will be
+ accepted only for superusers and roles with privileges of
+ <literal>pg_use_reserved_connections</literal>. If
+ <varname>superuser_reserved_connections</varname> or fewer connection
+ slots are available, new connections will be accepted only for
+ superusers.
+ </para>
+
+ <para>
+ The default value is zero connections. The value must be less than
+ <varname>max_connections</varname> minus
+ <varname>superuser_reserved_connections</varname>. This parameter can
+ only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-unix-socket-directories" xreflabel="unix_socket_directories">
<term><varname>unix_socket_directories</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 71a2d8f298..d553b1adab 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -689,6 +689,11 @@ DROP ROLE doomed_role;
and <link linkend="sql-lock"><command>LOCK TABLE</command></link> on all
relations.</entry>
</row>
+ <row>
+ <entry>pg_use_reserved_backends</entry>
+ <entry>Allow use of connection slots reserved via
+ <xref linkend="guc-reserved-connections"/>.</entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 470704f364..ac69d3fd9b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -205,14 +205,23 @@ char *ListenAddresses;
/*
* SuperuserReservedBackends is the number of backends reserved for superuser
- * use. This number is taken out of the pool size given by MaxConnections so
- * number of backend slots available to non-superusers is
- * (MaxConnections - SuperuserReservedBackends). Note what this really means
- * is "if there are <= SuperuserReservedBackends connections available, only
- * superusers can make new connections" --- pre-existing superuser connections
- * don't count against the limit.
+ * use, and ReservedBackends is the number of backends reserved for use by
+ * roles with privileges of the pg_use_reserved_backends predefined role.
+ * These are taken out of the pool of MaxConnections backend slots, so the
+ * number of backend slots available for roles that are neither superuser nor
+ * have privileges of pg_use_reserved_backends is
+ * (MaxConnections - SuperuserReservedBackends - ReservedBackends).
+ *
+ * If the number of remaining slots is less than or equal to
+ * SuperuserReservedBackends, only superusers can make new connections. If the
+ * number of remaining slots is greater than SuperuserReservedBackends but less
+ * than or equal to (SuperuserReservedBackends + ReservedBackends), only
+ * superusers and roles with privileges of pg_use_reserved_backends can make
+ * new connections. Note that pre-existing superuser and
+ * pg_use_reserved_backends connections don't count against the limits.
*/
int SuperuserReservedBackends;
+int ReservedBackends;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +917,12 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (SuperuserReservedBackends >= MaxConnections)
+ if (SuperuserReservedBackends + ReservedBackends >= MaxConnections)
{
- write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
+ write_stderr("%s: superuser_reserved_connections (%d) plus reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- SuperuserReservedBackends, MaxConnections);
+ SuperuserReservedBackends, ReservedBackends,
+ MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 00d26dc0f6..bcd5363511 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -651,12 +651,14 @@ GetStartupBufferPinWaitBufId(void)
}
/*
- * Check whether there are at least N free PGPROC objects.
+ * Check whether there are at least N free PGPROC objects. If false is
+ * returned, *nfree will be set to the number of free PGPROC objects.
+ * Otherwise, *nfree will be set to n.
*
* Note: this is designed on the assumption that N will generally be small.
*/
bool
-HaveNFreeProcs(int n)
+HaveNFreeProcs(int n, int *nfree)
{
PGPROC *proc;
@@ -664,15 +666,16 @@ HaveNFreeProcs(int n)
proc = ProcGlobal->freeProcs;
- while (n > 0 && proc != NULL)
+ *nfree = 0;
+ while (*nfree < n && proc != NULL)
{
proc = (PGPROC *) proc->links.next;
- n--;
+ (*nfree)++;
}
SpinLockRelease(ProcStructLock);
- return (n <= 0);
+ return (*nfree == n);
}
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6fa696fe8d..2a007f633b 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -719,6 +719,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
bool am_superuser;
char *fullpath;
char dbname[NAMEDATALEN];
+ int nfree = 0;
elog(DEBUG3, "InitPostgres");
@@ -922,16 +923,26 @@ InitPostgres(const char *in_dbname, Oid dboid,
}
/*
- * The last few connection slots are reserved for superusers. Replication
- * connections are drawn from slots reserved with max_wal_senders and not
- * limited by max_connections or superuser_reserved_connections.
+ * The last few connection slots are reserved for superusers and roles with
+ * privileges of pg_use_reserved_connections. Replication connections are
+ * drawn from slots reserved with max_wal_senders and are not limited by
+ * max_connections, superuser_reserved_connections, or
+ * reserved_connections.
*/
if (!am_superuser && !am_walsender &&
- SuperuserReservedBackends > 0 &&
- !HaveNFreeProcs(SuperuserReservedBackends))
- ereport(FATAL,
- (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for non-replication superuser connections")));
+ (SuperuserReservedBackends + ReservedBackends) > 0 &&
+ !HaveNFreeProcs(SuperuserReservedBackends + ReservedBackends, &nfree))
+ {
+ if (nfree < SuperuserReservedBackends)
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for non-replication superuser connections")));
+
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_backends")));
+ }
/* Check replication permissions needed for walsender processes. */
if (am_walsender)
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5aa2cda8f9..77b17aa7a6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2168,6 +2168,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"reserved_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+ gettext_noop("Sets the number of connection slots reserved for roles "
+ "with privileges of pg_use_reserved_connections."),
+ NULL
+ },
+ &ReservedBackends,
+ 0, 0, MAX_BACKENDS,
+ NULL, NULL, NULL
+ },
+
{
{"min_dynamic_shared_memory", PGC_POSTMASTER, RESOURCES_MEM,
gettext_noop("Amount of dynamic shared memory reserved at startup."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..f701312225 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -64,6 +64,7 @@
#port = 5432 # (change requires restart)
#max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
+#reserved_connections = 0 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
# (change requires restart)
#unix_socket_group = '' # (change requires restart)
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 2a2fee7d28..f2e5663c9f 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -89,5 +89,10 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
+ rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' },
]
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 168d85a3d1..18ef5afa75 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -16,6 +16,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
extern PGDLLIMPORT int SuperuserReservedBackends;
+extern PGDLLIMPORT int ReservedBackends;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index b5c6f46d03..5221014737 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -445,7 +445,7 @@ extern void InitAuxiliaryProcess(void);
extern void SetStartupBufferPinWaitBufId(int bufid);
extern int GetStartupBufferPinWaitBufId(void);
-extern bool HaveNFreeProcs(int n);
+extern bool HaveNFreeProcs(int n, int *nfree);
extern void ProcReleaseLocks(bool isCommit);
extern void ProcQueueInit(PROC_QUEUE *queue);
--
2.25.1
On Wed, Jan 18, 2023 at 2:00 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Wed, Jan 18, 2023 at 11:28:57AM -0500, Robert Haas wrote:
In general, looks good. I think this will often call HaveNFreeProcs
twice, though, and that would be better to avoid, e.g.I should have thought of this. This is fixed in v2.
Should (nfree < SuperuserReservedBackends) be using <=, or am I confused?
In the common case where we hit neither limit, this only counts free
connection slots once. We could do even better by making
HaveNFreeProcs have an out parameter for the number of free procs
actually found when it returns false, but that's probably not
important.Actually, I think it might be important. IIUC the separate calls to
HaveNFreeProcs might return different values for the same input, which
could result in incorrect error messages (e.g., you might get the
reserved_connections message despite setting reserved_connections to 0).
So, I made this change in v2, too.
I thought of that briefly and it didn't seem that important, but the
way you did it seems fine, so let's go with that.
What's the deal with removing "and no new replication connections will
be accepted" from the documentation? Is the existing documentation
just wrong? If so, should we fix that first? And maybe delete
"non-replication" from the error message that says "remaining
connection slots are reserved for non-replication superuser
connections"? It seems like right now the comments say that
replication connections are a completely separate pool of connections,
but the documentation and the error message make it sound otherwise.
If that's true, then one of them is wrong, and I think it's the
docs/error message. Or am I just misreading it?
--
Robert Haas
EDB: http://www.enterprisedb.com
On Wed, Jan 18, 2023 at 02:51:38PM -0500, Robert Haas wrote:
Should (nfree < SuperuserReservedBackends) be using <=, or am I confused?
I believe < is correct. At this point, the new backend will have already
claimed a proc struct, so if the number of remaining free slots equals the
number of reserved slots, it is okay.
What's the deal with removing "and no new replication connections will
be accepted" from the documentation? Is the existing documentation
just wrong? If so, should we fix that first? And maybe delete
"non-replication" from the error message that says "remaining
connection slots are reserved for non-replication superuser
connections"? It seems like right now the comments say that
replication connections are a completely separate pool of connections,
but the documentation and the error message make it sound otherwise.
If that's true, then one of them is wrong, and I think it's the
docs/error message. Or am I just misreading it?
I think you are right. This seems to have been missed in ea92368. I moved
this part to a new patch that should probably be back-patched to v12.
On that note, I wonder if it's worth changing the "sorry, too many clients
already" message to make it clear that max_connections has been reached.
IME some users are confused by this error, and I think it would be less
confusing if it pointed to the parameter that governs the number of
connection slots. I'll create a new thread for this.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v3-0001-Code-review-for-ea92368.patchtext/x-diff; charset=us-asciiDownload
From 8f0aa2fa54ae01149cffe9a69265f98e76a08a23 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 18 Jan 2023 12:43:41 -0800
Subject: [PATCH v3 1/3] Code review for ea92368.
This commit missed an error message and a line in the docs.
Back-patch to v12.
---
doc/src/sgml/config.sgml | 3 +--
src/backend/utils/init/postinit.c | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89d53f2a64..e019a1aac9 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -725,8 +725,7 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
- connections will be accepted only for superusers, and no
- new replication connections will be accepted.
+ connections will be accepted only for superusers.
</para>
<para>
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index ae5a85ed65..9145d96b38 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -931,7 +931,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
!HaveNFreeProcs(ReservedBackends))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for non-replication superuser connections")));
+ errmsg("remaining connection slots are reserved for superusers")));
/* Check replication permissions needed for walsender processes. */
if (am_walsender)
--
2.25.1
v3-0002-Rename-ReservedBackends-to-SuperuserReservedBacke.patchtext/x-diff; charset=us-asciiDownload
From bc110461e4f0a73c2b76ba0a6e821349c2cbe3df Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 13:58:56 -0800
Subject: [PATCH v3 2/3] Rename ReservedBackends to SuperuserReservedBackends.
This is in preparation for adding a new reserved_connections GUC
that will use the ReservedBackends variable name.
---
src/backend/postmaster/postmaster.c | 18 +++++++++---------
src/backend/utils/init/postinit.c | 4 ++--
src/backend/utils/misc/guc_tables.c | 2 +-
src/include/postmaster/postmaster.h | 2 +-
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9cedc1b9f0..470704f364 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -204,15 +204,15 @@ char *Unix_socket_directories;
char *ListenAddresses;
/*
- * ReservedBackends is the number of backends reserved for superuser use.
- * This number is taken out of the pool size given by MaxConnections so
+ * SuperuserReservedBackends is the number of backends reserved for superuser
+ * use. This number is taken out of the pool size given by MaxConnections so
* number of backend slots available to non-superusers is
- * (MaxConnections - ReservedBackends). Note what this really means is
- * "if there are <= ReservedBackends connections available, only superusers
- * can make new connections" --- pre-existing superuser connections don't
- * count against the limit.
+ * (MaxConnections - SuperuserReservedBackends). Note what this really means
+ * is "if there are <= SuperuserReservedBackends connections available, only
+ * superusers can make new connections" --- pre-existing superuser connections
+ * don't count against the limit.
*/
-int ReservedBackends;
+int SuperuserReservedBackends;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +908,11 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (ReservedBackends >= MaxConnections)
+ if (SuperuserReservedBackends >= MaxConnections)
{
write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- ReservedBackends, MaxConnections);
+ SuperuserReservedBackends, MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 9145d96b38..531a80f8d5 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -927,8 +927,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
* limited by max_connections or superuser_reserved_connections.
*/
if (!am_superuser && !am_walsender &&
- ReservedBackends > 0 &&
- !HaveNFreeProcs(ReservedBackends))
+ SuperuserReservedBackends > 0 &&
+ !HaveNFreeProcs(SuperuserReservedBackends))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for superusers")));
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..5aa2cda8f9 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2163,7 +2163,7 @@ struct config_int ConfigureNamesInt[] =
gettext_noop("Sets the number of connection slots reserved for superusers."),
NULL
},
- &ReservedBackends,
+ &SuperuserReservedBackends,
3, 0, MAX_BACKENDS,
NULL, NULL, NULL
},
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 203177e1ff..168d85a3d1 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -15,7 +15,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
-extern PGDLLIMPORT int ReservedBackends;
+extern PGDLLIMPORT int SuperuserReservedBackends;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
--
2.25.1
v3-0003-Introduce-reserved_connections-and-pg_use_reserve.patchtext/x-diff; charset=us-asciiDownload
From 17bd6189138c49bc222614195cbf202274ea8e9b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 15:36:59 -0800
Subject: [PATCH v3 3/3] Introduce reserved_connections and
pg_use_reserved_connections.
This provides a way to reserve connection slots for non-superusers.
superuser_reserved_connections remains as a final reserve in case
reserved_connections has been exhausted.
---
doc/src/sgml/config.sgml | 39 ++++++++++++++++++-
doc/src/sgml/user-manag.sgml | 5 +++
src/backend/postmaster/postmaster.c | 28 ++++++++-----
src/backend/storage/lmgr/proc.c | 16 +++++---
src/backend/utils/init/postinit.c | 27 +++++++++----
src/backend/utils/misc/guc_tables.c | 11 ++++++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_authid.dat | 5 +++
src/include/postmaster/postmaster.h | 1 +
src/include/storage/proc.h | 2 +-
10 files changed, 110 insertions(+), 25 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e019a1aac9..626faa4d08 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -725,17 +725,52 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
- connections will be accepted only for superusers.
+ connections will be accepted only for superusers. The connection slots
+ reserved by this parameter are intended as final reserve for emergency
+ use after the slots reserved by
+ <xref linkend="guc-reserved-connections"/> have been exhausted.
</para>
<para>
The default value is three connections. The value must be less
- than <varname>max_connections</varname>.
+ than <varname>max_connections</varname> minus
+ <varname>reserved_connections</varname>.
This parameter can only be set at server start.
</para>
</listitem>
</varlistentry>
+ <varlistentry id="guc-reserved-connections" xreflabel="reserved_connections">
+ <term><varname>reserved_connections</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>reserved_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Determines the number of connection <quote>slots</quote> that are
+ reserved for connections by roles with privileges of the
+ <link linkend="predefined-roles-table"><literal>pg_used_reserved_connections</literal></link>
+ role. Whenever the number of free connection slots is greater than
+ <xref linkend="guc-superuser-reserved-connections"/> but less than or
+ equal to the sum of <varname>superuser_reserved_connections</varname>
+ and <varname>reserved_connections</varname>, new connections will be
+ accepted only for superusers and roles with privileges of
+ <literal>pg_use_reserved_connections</literal>. If
+ <varname>superuser_reserved_connections</varname> or fewer connection
+ slots are available, new connections will be accepted only for
+ superusers.
+ </para>
+
+ <para>
+ The default value is zero connections. The value must be less than
+ <varname>max_connections</varname> minus
+ <varname>superuser_reserved_connections</varname>. This parameter can
+ only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-unix-socket-directories" xreflabel="unix_socket_directories">
<term><varname>unix_socket_directories</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 71a2d8f298..d553b1adab 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -689,6 +689,11 @@ DROP ROLE doomed_role;
and <link linkend="sql-lock"><command>LOCK TABLE</command></link> on all
relations.</entry>
</row>
+ <row>
+ <entry>pg_use_reserved_backends</entry>
+ <entry>Allow use of connection slots reserved via
+ <xref linkend="guc-reserved-connections"/>.</entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 470704f364..ac69d3fd9b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -205,14 +205,23 @@ char *ListenAddresses;
/*
* SuperuserReservedBackends is the number of backends reserved for superuser
- * use. This number is taken out of the pool size given by MaxConnections so
- * number of backend slots available to non-superusers is
- * (MaxConnections - SuperuserReservedBackends). Note what this really means
- * is "if there are <= SuperuserReservedBackends connections available, only
- * superusers can make new connections" --- pre-existing superuser connections
- * don't count against the limit.
+ * use, and ReservedBackends is the number of backends reserved for use by
+ * roles with privileges of the pg_use_reserved_backends predefined role.
+ * These are taken out of the pool of MaxConnections backend slots, so the
+ * number of backend slots available for roles that are neither superuser nor
+ * have privileges of pg_use_reserved_backends is
+ * (MaxConnections - SuperuserReservedBackends - ReservedBackends).
+ *
+ * If the number of remaining slots is less than or equal to
+ * SuperuserReservedBackends, only superusers can make new connections. If the
+ * number of remaining slots is greater than SuperuserReservedBackends but less
+ * than or equal to (SuperuserReservedBackends + ReservedBackends), only
+ * superusers and roles with privileges of pg_use_reserved_backends can make
+ * new connections. Note that pre-existing superuser and
+ * pg_use_reserved_backends connections don't count against the limits.
*/
int SuperuserReservedBackends;
+int ReservedBackends;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +917,12 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (SuperuserReservedBackends >= MaxConnections)
+ if (SuperuserReservedBackends + ReservedBackends >= MaxConnections)
{
- write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
+ write_stderr("%s: superuser_reserved_connections (%d) plus reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- SuperuserReservedBackends, MaxConnections);
+ SuperuserReservedBackends, ReservedBackends,
+ MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index f8ac4edd6f..22b4278610 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -645,27 +645,33 @@ GetStartupBufferPinWaitBufId(void)
}
/*
- * Check whether there are at least N free PGPROC objects.
+ * Check whether there are at least N free PGPROC objects. If false is
+ * returned, *nfree will be set to the number of free PGPROC objects.
+ * Otherwise, *nfree will be set to n.
*
* Note: this is designed on the assumption that N will generally be small.
*/
bool
-HaveNFreeProcs(int n)
+HaveNFreeProcs(int n, int *nfree)
{
dlist_iter iter;
+ Assert(n > 0);
+ Assert(nfree);
+
SpinLockAcquire(ProcStructLock);
+ *nfree = 0;
dlist_foreach(iter, &ProcGlobal->freeProcs)
{
- n--;
- if (n == 0)
+ (*nfree)++;
+ if (*nfree == n)
break;
}
SpinLockRelease(ProcStructLock);
- return (n <= 0);
+ return (*nfree == n);
}
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 531a80f8d5..bd7ece259a 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -719,6 +719,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
bool am_superuser;
char *fullpath;
char dbname[NAMEDATALEN];
+ int nfree = 0;
elog(DEBUG3, "InitPostgres");
@@ -922,16 +923,26 @@ InitPostgres(const char *in_dbname, Oid dboid,
}
/*
- * The last few connection slots are reserved for superusers. Replication
- * connections are drawn from slots reserved with max_wal_senders and not
- * limited by max_connections or superuser_reserved_connections.
+ * The last few connection slots are reserved for superusers and roles with
+ * privileges of pg_use_reserved_connections. Replication connections are
+ * drawn from slots reserved with max_wal_senders and are not limited by
+ * max_connections, superuser_reserved_connections, or
+ * reserved_connections.
*/
if (!am_superuser && !am_walsender &&
- SuperuserReservedBackends > 0 &&
- !HaveNFreeProcs(SuperuserReservedBackends))
- ereport(FATAL,
- (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for superusers")));
+ (SuperuserReservedBackends + ReservedBackends) > 0 &&
+ !HaveNFreeProcs(SuperuserReservedBackends + ReservedBackends, &nfree))
+ {
+ if (nfree < SuperuserReservedBackends)
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for superusers")));
+
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_backends")));
+ }
/* Check replication permissions needed for walsender processes. */
if (am_walsender)
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5aa2cda8f9..77b17aa7a6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2168,6 +2168,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"reserved_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+ gettext_noop("Sets the number of connection slots reserved for roles "
+ "with privileges of pg_use_reserved_connections."),
+ NULL
+ },
+ &ReservedBackends,
+ 0, 0, MAX_BACKENDS,
+ NULL, NULL, NULL
+ },
+
{
{"min_dynamic_shared_memory", PGC_POSTMASTER, RESOURCES_MEM,
gettext_noop("Amount of dynamic shared memory reserved at startup."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..f701312225 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -64,6 +64,7 @@
#port = 5432 # (change requires restart)
#max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
+#reserved_connections = 0 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
# (change requires restart)
#unix_socket_group = '' # (change requires restart)
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 2a2fee7d28..f2e5663c9f 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -89,5 +89,10 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
+ rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' },
]
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 168d85a3d1..18ef5afa75 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -16,6 +16,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
extern PGDLLIMPORT int SuperuserReservedBackends;
+extern PGDLLIMPORT int ReservedBackends;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index dd45b8ee9b..4258cd92c9 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -445,7 +445,7 @@ extern void InitAuxiliaryProcess(void);
extern void SetStartupBufferPinWaitBufId(int bufid);
extern int GetStartupBufferPinWaitBufId(void);
-extern bool HaveNFreeProcs(int n);
+extern bool HaveNFreeProcs(int n, int *nfree);
extern void ProcReleaseLocks(bool isCommit);
extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable);
--
2.25.1
On Wed, Jan 18, 2023 at 12:15:33PM -0500, Robert Haas wrote:
On Mon, Jan 16, 2023 at 2:29 PM Robert Haas <robertmhaas@gmail.com> wrote:
1. It's still possible for a CREATEROLE user to hand out role
attributes that they don't possess. The new prohibitions in
cf5eb37c5ee0cc54c80d95c1695d7fca1f7c68cb prevent a CREATEROLE user
from handing out membership in a role on which they lack sufficient
permissions, but they don't prevent a CREATEROLE user who lacks
CREATEDB from creating a new user who does have CREATEDB. I think we
should subject the CREATEDB, REPLICATION, and BYPASSRLS attributes to
the same rule that we now use for role memberships: you've got to have
the property in order to give it to someone else. In the case of
CREATEDB, this would tighten the current rules, which allow you to
give out CREATEDB without having it. In the case of REPLICATION and
BYPASSRLS, this would liberalize the current rules: right now, a
CREATEROLE user cannot give REPLICATION or BYPASSRLS to another user
even if they possess those attributes.This proposal doesn't address the CREATEROLE or CONNECTION LIMIT
properties. It seems possible to me that someone might want to set up
a CREATEROLE user who can't make more such users, and this proposal
doesn't manufacture any way of doing that. It also doesn't let you
constraint the ability of a CREATEROLE user to set a CONNECTION LIMIT
for some other user. I think that's OK. It might be nice to have ways
of imposing such restrictions at some point in the future, but it is
not very obvious what to do about such cases and, importantly, I don't
think there's any security impact from failing to address those cases.
If a CREATEROLE user without CREATEDB can create a new role that does
have CREATEDB, that's a privilege escalation. If they can hand out
CREATEROLE, that isn't: they already have it.Here is a patch implementing the above proposal. Since this is fairly
closely related to already-committed work, I would like to get this
into v16. That way, all the changes to how CREATEROLE works will go
into a single release, which seems less confusing for users. It is
also fairly clear to me that this is an improvement over the status
quo. Sometimes things that seem clear to me turn out to be false, so
if this change seems like a problem to you, please let me know.
This seems like a clear improvement to me. However, as the attribute
system becomes more sophisticated, I think we ought to improve the error
messages in user.c. IMHO messages like "permission denied" could be
greatly improved with some added context.
For example, if I want to change a role's password, I need both CREATEROLE
and ADMIN OPTION on the role, but the error message only mentions
CREATEROLE.
postgres=# create role createrole with createrole;
CREATE ROLE
postgres=# create role otherrole;
CREATE ROLE
postgres=# set role createrole;
SET
postgres=> alter role otherrole password 'test';
ERROR: must have CREATEROLE privilege to change another user's password
Similarly, if I want to allow a role to grant REPLICATION to another role,
I have to give it CREATEROLE, REPLICATION, and membership with ADMIN
OPTION. If the role is missing CREATEROLE or membership with ADMIN OPTION,
it'll only ever see a "permission denied" error.
postgres=# create role createrole with createrole;
CREATE ROLE
postgres=# create role otherrole;
CREATE ROLE
postgres=# set role createrole;
SET
postgres=> alter role otherrole with replication;
ERROR: permission denied
postgres=> reset role;
RESET
postgres=# alter role createrole with replication;
ALTER ROLE
postgres=# set role createrole;
SET
postgres=> alter role otherrole with replication;
ERROR: permission denied
postgres=> reset role;
RESET
postgres=# grant otherrole to createrole;
GRANT ROLE
postgres=# set role createrole;
SET
postgres=> alter role otherrole with replication;
ERROR: permission denied
postgres=> reset role;
RESET
postgres=# grant otherrole to createrole with admin option;
GRANT ROLE
postgres=# set role createrole;
SET
postgres=> alter role otherrole with replication;
ALTER ROLE
If it has both CREATEROLE and membership with ADMIN OPTION (but not
REPLICATION), it'll see a more helpful message.
postgres=# create role createrole with createrole;
CREATE ROLE
postgres=# create role otherrole;
CREATE ROLE
postgres=# grant otherrole to createrole with admin option;
GRANT ROLE
postgres=# set role createrole;
SET
postgres=> alter role otherrole with replication;
ERROR: must have replication privilege to change replication attribute
This probably shouldn't block your patch, but I think it's worth doing in
v16 since there are other changes in this area. I'm happy to help.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On 1/19/23 4:47 AM, Nathan Bossart wrote:
This seems like a clear improvement to me. However, as the attribute
system becomes more sophisticated, I think we ought to improve the error
messages in user.c. IMHO messages like "permission denied" could be
greatly improved with some added context.
I observed this behavior where the role is having creatrole but still
it's unable to pass it to another user.
postgres=# create role abc1 login createrole;
CREATE ROLE
postgres=# create user test1;
CREATE ROLE
postgres=# \c - abc1
You are now connected to database "postgres" as user "abc1".
postgres=> alter role test1 with createrole ;
ERROR: permission denied
postgres=>
which was working previously without patch.
Is this an expected behavior?
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On 1/19/23 3:05 PM, tushar wrote:
which was working previously without patch.
My bad, I was testing against PG v15 but this issue is not
reproducible on master (without patch).
As you mentioned- "This implements the standard idea that you can't give
permissions
you don't have (but you can give the ones you do have)" but here the
role is having
createrole privilege that he cannot pass on to another user? Is this
expected?
postgres=# create role fff with createrole;
CREATE ROLE
postgres=# create role xxx;
CREATE ROLE
postgres=# set role fff;
SET
postgres=> alter role xxx with createrole;
ERROR: permission denied
postgres=>
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On 1/19/23 2:44 AM, Nathan Bossart wrote:
On Wed, Jan 18, 2023 at 02:51:38PM -0500, Robert Haas wrote:
Should (nfree < SuperuserReservedBackends) be using <=, or am I confused?
I believe < is correct. At this point, the new backend will have already
claimed a proc struct, so if the number of remaining free slots equals the
number of reserved slots, it is okay.What's the deal with removing "and no new replication connections will
be accepted" from the documentation? Is the existing documentation
just wrong? If so, should we fix that first? And maybe delete
"non-replication" from the error message that says "remaining
connection slots are reserved for non-replication superuser
connections"? It seems like right now the comments say that
replication connections are a completely separate pool of connections,
but the documentation and the error message make it sound otherwise.
If that's true, then one of them is wrong, and I think it's the
docs/error message. Or am I just misreading it?I think you are right. This seems to have been missed in ea92368. I moved
this part to a new patch that should probably be back-patched to v12.On that note, I wonder if it's worth changing the "sorry, too many clients
already" message to make it clear that max_connections has been reached.
IME some users are confused by this error, and I think it would be less
confusing if it pointed to the parameter that governs the number of
connection slots. I'll create a new thread for this.
There is� one typo , for the doc changes, it is� mentioned
"pg_use_reserved_backends" but i think it supposed to be
"pg_use_reserved_connections"
under Table 22.1. Predefined Roles.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On Thu, Jan 19, 2023 at 6:28 PM tushar <tushar.ahuja@enterprisedb.com>
wrote:
On 1/19/23 2:44 AM, Nathan Bossart wrote:
On Wed, Jan 18, 2023 at 02:51:38PM -0500, Robert Haas wrote:
Should (nfree < SuperuserReservedBackends) be using <=, or am I
confused?
I believe < is correct. At this point, the new backend will have already
claimed a proc struct, so if the number of remaining free slots equalsthe
number of reserved slots, it is okay.
What's the deal with removing "and no new replication connections will
be accepted" from the documentation? Is the existing documentation
just wrong? If so, should we fix that first? And maybe delete
"non-replication" from the error message that says "remaining
connection slots are reserved for non-replication superuser
connections"? It seems like right now the comments say that
replication connections are a completely separate pool of connections,
but the documentation and the error message make it sound otherwise.
If that's true, then one of them is wrong, and I think it's the
docs/error message. Or am I just misreading it?I think you are right. This seems to have been missed in ea92368. I
moved
this part to a new patch that should probably be back-patched to v12.
On that note, I wonder if it's worth changing the "sorry, too many
clients
already" message to make it clear that max_connections has been reached.
IME some users are confused by this error, and I think it would be less
confusing if it pointed to the parameter that governs the number of
connection slots. I'll create a new thread for this.There is one typo , for the doc changes, it is mentioned
"pg_use_reserved_backends" but i think it supposed to be
"pg_use_reserved_connections"
under Table 22.1. Predefined Roles.and in the error message too
[edb@centos7tushar bin]$ ./psql postgres -U r2
psql: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed:
FATAL: remaining connection slots are reserved for roles with privileges
of pg_use_reserved_backends
[edb@centos7tushar bin]$
regards,
On Thu, Jan 19, 2023 at 6:50 PM tushar <tushar.ahuja@enterprisedb.com>
wrote:
and in the error message too
[edb@centos7tushar bin]$ ./psql postgres -U r2
psql: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed:
FATAL: remaining connection slots are reserved for roles with privileges
of pg_use_reserved_backends
[edb@centos7tushar bin]$
I think there is also a need to improve the error message if non
super users are not able to connect due to slot unavailability.
--Connect to psql terminal, create a user
create user t1;
--set these GUC parameters in postgresql.conf and restart the server
max_connections = 3 # (change requires restart)
superuser_reserved_connections = 1 # (change requires restart)
reserved_connections = 1
psql terminal ( connect to superuser), ./psql postgres
psql terminal (try to connect to user t1) , ./psql postgres -U t1
Error message is
psql: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed:
FATAL: remaining connection slots are reserved for roles with privileges
of pg_use_reserved_backends
that is not true because the superuser can still able to connect,
probably in this case message should be like this -
"remaining connection slots are reserved for roles with privileges of
pg_use_reserved_connections and for superusers" or something better.
regards,
On Thu, Jan 19, 2023 at 6:15 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
postgres=# create role fff with createrole;
CREATE ROLE
postgres=# create role xxx;
CREATE ROLE
postgres=# set role fff;
SET
postgres=> alter role xxx with createrole;
ERROR: permission denied
postgres=>
Here fff would need ADMIN OPTION on xxx to be able to make modifications to it.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Wed, Jan 18, 2023 at 6:17 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
Here is a patch implementing the above proposal. Since this is fairly
closely related to already-committed work, I would like to get this
into v16. That way, all the changes to how CREATEROLE works will go
into a single release, which seems less confusing for users. It is
also fairly clear to me that this is an improvement over the status
quo. Sometimes things that seem clear to me turn out to be false, so
if this change seems like a problem to you, please let me know.This seems like a clear improvement to me.
Cool.
However, as the attribute
system becomes more sophisticated, I think we ought to improve the error
messages in user.c. IMHO messages like "permission denied" could be
greatly improved with some added context.This probably shouldn't block your patch, but I think it's worth doing in
v16 since there are other changes in this area. I'm happy to help.
That would be great. I agree that it's good to try to improve the
error messages. It hasn't been entirely clear to me how to do that.
For instance, I don't think we want to say something like:
ERROR: must have CREATEROLE privilege and ADMIN OPTION on the target
role, or in lieu of both of those to be superuser, to set the
CONNECTION LIMIT for another role
ERROR: must have CREATEROLE privilege and ADMIN OPTION on the target
role, plus also CREATEDB, or in lieu of all that to be superuser, to
remove the CREATEDB property from another role
Such messages are long and we'd end up with a lot of variants. It's
possible that the messages could be multi-tier. For instance, if we
determine that you're trying to manage users and you don't have
permission to manage ANY user, we could say:
ERROR: permission to manage roles denied
DETAIL: You must have the CREATEROLE privilege or be a superuser to
manage roles.
If you could potentially manage some user, but not the one you're
trying to manage, we could say:
ERROR: permission to manage role "%s" denied
DETAIL: You need ADMIN OPTION on the target role to manage it.
If you have permission to manage the target role but not in the
requested manner, we could then say something like:
ERROR: permission to manage CREATEDB for role "%s" denied
DETAIL: You need CREATEDB to manage it.
This is just one idea, and maybe not the best one. I'm just trying to
say that I think this is basically an organizational problem. We need
a plan for how we're going to report errors that is not too
complicated to implement with reasonable effort, and that will produce
messages that users will understand. I'd be delighted if you wanted to
provide either ideas or patches...
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jan 19, 2023 at 9:21 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
that is not true because the superuser can still able to connect,
It is true, but because superusers have all privileges.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Wed, Jan 18, 2023 at 4:14 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Wed, Jan 18, 2023 at 02:51:38PM -0500, Robert Haas wrote:
Should (nfree < SuperuserReservedBackends) be using <=, or am I confused?
I believe < is correct. At this point, the new backend will have already
claimed a proc struct, so if the number of remaining free slots equals the
number of reserved slots, it is okay.
OK. Might be worth a short comment.
What's the deal with removing "and no new replication connections will
be accepted" from the documentation? Is the existing documentation
just wrong? If so, should we fix that first? And maybe delete
"non-replication" from the error message that says "remaining
connection slots are reserved for non-replication superuser
connections"? It seems like right now the comments say that
replication connections are a completely separate pool of connections,
but the documentation and the error message make it sound otherwise.
If that's true, then one of them is wrong, and I think it's the
docs/error message. Or am I just misreading it?I think you are right. This seems to have been missed in ea92368. I moved
this part to a new patch that should probably be back-patched to v12.
I'm inclined to commit it to master and not back-patch. It doesn't
seem important enough to perturb translations.
Tushar seems to have a point about pg_use_reserved_connections vs.
pg_use_reserved_backends. I think we should standardize on the former,
as backends is an internal term.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jan 19, 2023 at 11:40:53AM -0500, Robert Haas wrote:
On Wed, Jan 18, 2023 at 4:14 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Wed, Jan 18, 2023 at 02:51:38PM -0500, Robert Haas wrote:
Should (nfree < SuperuserReservedBackends) be using <=, or am I confused?
I believe < is correct. At this point, the new backend will have already
claimed a proc struct, so if the number of remaining free slots equals the
number of reserved slots, it is okay.OK. Might be worth a short comment.
I added one.
What's the deal with removing "and no new replication connections will
be accepted" from the documentation? Is the existing documentation
just wrong? If so, should we fix that first? And maybe delete
"non-replication" from the error message that says "remaining
connection slots are reserved for non-replication superuser
connections"? It seems like right now the comments say that
replication connections are a completely separate pool of connections,
but the documentation and the error message make it sound otherwise.
If that's true, then one of them is wrong, and I think it's the
docs/error message. Or am I just misreading it?I think you are right. This seems to have been missed in ea92368. I moved
this part to a new patch that should probably be back-patched to v12.I'm inclined to commit it to master and not back-patch. It doesn't
seem important enough to perturb translations.
That seems reasonable to me.
Tushar seems to have a point about pg_use_reserved_connections vs.
pg_use_reserved_backends. I think we should standardize on the former,
as backends is an internal term.
Oops. This is what I meant to do. I probably flubbed it because I was
wondering why the parameter uses "connections" and the variable uses
"backends," especially considering that the variable for max_connections is
called MaxConnections. I went ahead and renamed everything to use
"connections."
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v4-0001-Code-review-for-ea92368.patchtext/x-diff; charset=us-asciiDownload
From 7be7e70aaf488a924d61b21b351a3b4f7e33aedc Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 18 Jan 2023 12:43:41 -0800
Subject: [PATCH v4 1/3] Code review for ea92368.
This commit missed an error message and a line in the docs.
---
doc/src/sgml/config.sgml | 3 +--
src/backend/utils/init/postinit.c | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89d53f2a64..e019a1aac9 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -725,8 +725,7 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
- connections will be accepted only for superusers, and no
- new replication connections will be accepted.
+ connections will be accepted only for superusers.
</para>
<para>
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index ae5a85ed65..9145d96b38 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -931,7 +931,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
!HaveNFreeProcs(ReservedBackends))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for non-replication superuser connections")));
+ errmsg("remaining connection slots are reserved for superusers")));
/* Check replication permissions needed for walsender processes. */
if (am_walsender)
--
2.25.1
v4-0002-Rename-ReservedBackends-to-SuperuserReservedConne.patchtext/x-diff; charset=us-asciiDownload
From 058df2b3dcf50ecbe76c794f4f52751e6a9f765f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 13:58:56 -0800
Subject: [PATCH v4 2/3] Rename ReservedBackends to
SuperuserReservedConnections.
This is in preparation for adding a new reserved_connections GUC.
---
src/backend/postmaster/postmaster.c | 20 ++++++++++----------
src/backend/utils/init/postinit.c | 4 ++--
src/backend/utils/misc/guc_tables.c | 2 +-
src/include/postmaster/postmaster.h | 2 +-
4 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9cedc1b9f0..3f799c4ac8 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -204,15 +204,15 @@ char *Unix_socket_directories;
char *ListenAddresses;
/*
- * ReservedBackends is the number of backends reserved for superuser use.
- * This number is taken out of the pool size given by MaxConnections so
- * number of backend slots available to non-superusers is
- * (MaxConnections - ReservedBackends). Note what this really means is
- * "if there are <= ReservedBackends connections available, only superusers
- * can make new connections" --- pre-existing superuser connections don't
- * count against the limit.
+ * SuperuserReservedConnections is the number of backends reserved for
+ * superuser use. This number is taken out of the pool size given by
+ * MaxConnections so number of backend slots available to non-superusers is
+ * (MaxConnections - SuperuserReservedConnections). Note what this really
+ * means is "if there are <= SuperuserReservedConnections connections
+ * available, only superusers can make new connections" --- pre-existing
+ * superuser connections don't count against the limit.
*/
-int ReservedBackends;
+int SuperuserReservedConnections;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +908,11 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (ReservedBackends >= MaxConnections)
+ if (SuperuserReservedConnections >= MaxConnections)
{
write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- ReservedBackends, MaxConnections);
+ SuperuserReservedConnections, MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 9145d96b38..40f145e0ab 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -927,8 +927,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
* limited by max_connections or superuser_reserved_connections.
*/
if (!am_superuser && !am_walsender &&
- ReservedBackends > 0 &&
- !HaveNFreeProcs(ReservedBackends))
+ SuperuserReservedConnections > 0 &&
+ !HaveNFreeProcs(SuperuserReservedConnections))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for superusers")));
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index cd0fc2cb8f..0fa9fdd3c5 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2163,7 +2163,7 @@ struct config_int ConfigureNamesInt[] =
gettext_noop("Sets the number of connection slots reserved for superusers."),
NULL
},
- &ReservedBackends,
+ &SuperuserReservedConnections,
3, 0, MAX_BACKENDS,
NULL, NULL, NULL
},
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 203177e1ff..0e4b8ded34 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -15,7 +15,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
-extern PGDLLIMPORT int ReservedBackends;
+extern PGDLLIMPORT int SuperuserReservedConnections;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
--
2.25.1
v4-0003-Introduce-reserved_connections-and-pg_use_reserve.patchtext/x-diff; charset=us-asciiDownload
From 3c137c30d287ea7388d77ee7f3a5757227638c51 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 15:36:59 -0800
Subject: [PATCH v4 3/3] Introduce reserved_connections and
pg_use_reserved_connections.
This provides a way to reserve connection slots for non-superusers.
superuser_reserved_connections remains as a final reserve in case
reserved_connections has been exhausted.
---
doc/src/sgml/config.sgml | 39 ++++++++++++++++++-
doc/src/sgml/user-manag.sgml | 5 +++
src/backend/postmaster/postmaster.c | 29 +++++++++-----
src/backend/storage/lmgr/proc.c | 16 +++++---
src/backend/utils/init/postinit.c | 31 +++++++++++----
src/backend/utils/misc/guc_tables.c | 11 ++++++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_authid.dat | 5 +++
src/include/postmaster/postmaster.h | 1 +
src/include/storage/proc.h | 2 +-
10 files changed, 115 insertions(+), 25 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e019a1aac9..626faa4d08 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -725,17 +725,52 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
- connections will be accepted only for superusers.
+ connections will be accepted only for superusers. The connection slots
+ reserved by this parameter are intended as final reserve for emergency
+ use after the slots reserved by
+ <xref linkend="guc-reserved-connections"/> have been exhausted.
</para>
<para>
The default value is three connections. The value must be less
- than <varname>max_connections</varname>.
+ than <varname>max_connections</varname> minus
+ <varname>reserved_connections</varname>.
This parameter can only be set at server start.
</para>
</listitem>
</varlistentry>
+ <varlistentry id="guc-reserved-connections" xreflabel="reserved_connections">
+ <term><varname>reserved_connections</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>reserved_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Determines the number of connection <quote>slots</quote> that are
+ reserved for connections by roles with privileges of the
+ <link linkend="predefined-roles-table"><literal>pg_used_reserved_connections</literal></link>
+ role. Whenever the number of free connection slots is greater than
+ <xref linkend="guc-superuser-reserved-connections"/> but less than or
+ equal to the sum of <varname>superuser_reserved_connections</varname>
+ and <varname>reserved_connections</varname>, new connections will be
+ accepted only for superusers and roles with privileges of
+ <literal>pg_use_reserved_connections</literal>. If
+ <varname>superuser_reserved_connections</varname> or fewer connection
+ slots are available, new connections will be accepted only for
+ superusers.
+ </para>
+
+ <para>
+ The default value is zero connections. The value must be less than
+ <varname>max_connections</varname> minus
+ <varname>superuser_reserved_connections</varname>. This parameter can
+ only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-unix-socket-directories" xreflabel="unix_socket_directories">
<term><varname>unix_socket_directories</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 71a2d8f298..002c1e3aff 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -689,6 +689,11 @@ DROP ROLE doomed_role;
and <link linkend="sql-lock"><command>LOCK TABLE</command></link> on all
relations.</entry>
</row>
+ <row>
+ <entry>pg_use_reserved_connections</entry>
+ <entry>Allow use of connection slots reserved via
+ <xref linkend="guc-reserved-connections"/>.</entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3f799c4ac8..aca1ef91b5 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -205,14 +205,24 @@ char *ListenAddresses;
/*
* SuperuserReservedConnections is the number of backends reserved for
- * superuser use. This number is taken out of the pool size given by
- * MaxConnections so number of backend slots available to non-superusers is
- * (MaxConnections - SuperuserReservedConnections). Note what this really
- * means is "if there are <= SuperuserReservedConnections connections
- * available, only superusers can make new connections" --- pre-existing
- * superuser connections don't count against the limit.
+ * superuser use, and ReservedConnections is the number of backends reserved
+ * for use by roles with privileges of the pg_use_reserved_connections
+ * predefined role. These are taken out of the pool of MaxConnections backend
+ * slots, so the number of backend slots available for roles that are neither
+ * superuser nor have privileges of pg_use_reserved_connections is
+ * (MaxConnections - SuperuserReservedConnections - ReservedConnections).
+ *
+ * If the number of remaining slots is less than or equal to
+ * SuperuserReservedConnections, only superusers can make new connections. If
+ * the number of remaining slots is greater than SuperuserReservedConnections
+ * but less than or equal to
+ * (SuperuserReservedConnections + ReservedConnections), only superusers and
+ * roles with privileges of pg_use_reserved_connections can make new
+ * connections. Note that pre-existing superuser and
+ * pg_use_reserved_connections connections don't count against the limits.
*/
int SuperuserReservedConnections;
+int ReservedConnections;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +918,12 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (SuperuserReservedConnections >= MaxConnections)
+ if (SuperuserReservedConnections + ReservedConnections >= MaxConnections)
{
- write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
+ write_stderr("%s: superuser_reserved_connections (%d) plus reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- SuperuserReservedConnections, MaxConnections);
+ SuperuserReservedConnections, ReservedConnections,
+ MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index f8ac4edd6f..22b4278610 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -645,27 +645,33 @@ GetStartupBufferPinWaitBufId(void)
}
/*
- * Check whether there are at least N free PGPROC objects.
+ * Check whether there are at least N free PGPROC objects. If false is
+ * returned, *nfree will be set to the number of free PGPROC objects.
+ * Otherwise, *nfree will be set to n.
*
* Note: this is designed on the assumption that N will generally be small.
*/
bool
-HaveNFreeProcs(int n)
+HaveNFreeProcs(int n, int *nfree)
{
dlist_iter iter;
+ Assert(n > 0);
+ Assert(nfree);
+
SpinLockAcquire(ProcStructLock);
+ *nfree = 0;
dlist_foreach(iter, &ProcGlobal->freeProcs)
{
- n--;
- if (n == 0)
+ (*nfree)++;
+ if (*nfree == n)
break;
}
SpinLockRelease(ProcStructLock);
- return (n <= 0);
+ return (*nfree == n);
}
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 40f145e0ab..2f07ca7a0e 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -719,6 +719,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
bool am_superuser;
char *fullpath;
char dbname[NAMEDATALEN];
+ int nfree = 0;
elog(DEBUG3, "InitPostgres");
@@ -922,16 +923,30 @@ InitPostgres(const char *in_dbname, Oid dboid,
}
/*
- * The last few connection slots are reserved for superusers. Replication
- * connections are drawn from slots reserved with max_wal_senders and not
- * limited by max_connections or superuser_reserved_connections.
+ * The last few connection slots are reserved for superusers and roles with
+ * privileges of pg_use_reserved_connections. Replication connections are
+ * drawn from slots reserved with max_wal_senders and are not limited by
+ * max_connections, superuser_reserved_connections, or
+ * reserved_connections.
+ *
+ * Note: At this point, the new backend has already claimed a proc struct,
+ * so we must check whether the number of free slots is strictly less than
+ * the reserved connection limits.
*/
if (!am_superuser && !am_walsender &&
- SuperuserReservedConnections > 0 &&
- !HaveNFreeProcs(SuperuserReservedConnections))
- ereport(FATAL,
- (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for superusers")));
+ (SuperuserReservedConnections + ReservedConnections) > 0 &&
+ !HaveNFreeProcs(SuperuserReservedConnections + ReservedConnections, &nfree))
+ {
+ if (nfree < SuperuserReservedConnections)
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for superusers")));
+
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_connections")));
+ }
/* Check replication permissions needed for walsender processes. */
if (am_walsender)
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0fa9fdd3c5..e1753a40fa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2168,6 +2168,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"reserved_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+ gettext_noop("Sets the number of connection slots reserved for roles "
+ "with privileges of pg_use_reserved_connections."),
+ NULL
+ },
+ &ReservedConnections,
+ 0, 0, MAX_BACKENDS,
+ NULL, NULL, NULL
+ },
+
{
{"min_dynamic_shared_memory", PGC_POSTMASTER, RESOURCES_MEM,
gettext_noop("Amount of dynamic shared memory reserved at startup."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..f701312225 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -64,6 +64,7 @@
#port = 5432 # (change requires restart)
#max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
+#reserved_connections = 0 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
# (change requires restart)
#unix_socket_group = '' # (change requires restart)
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 2a2fee7d28..f2e5663c9f 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -89,5 +89,10 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
+ rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' },
]
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 0e4b8ded34..3b3889c58c 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -16,6 +16,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
extern PGDLLIMPORT int SuperuserReservedConnections;
+extern PGDLLIMPORT int ReservedConnections;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index dd45b8ee9b..4258cd92c9 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -445,7 +445,7 @@ extern void InitAuxiliaryProcess(void);
extern void SetStartupBufferPinWaitBufId(int bufid);
extern int GetStartupBufferPinWaitBufId(void);
-extern bool HaveNFreeProcs(int n);
+extern bool HaveNFreeProcs(int n, int *nfree);
extern void ProcReleaseLocks(bool isCommit);
extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable);
--
2.25.1
On Thu, Jan 19, 2023 at 12:54 PM Nathan Bossart
<nathandbossart@gmail.com> wrote:
OK. Might be worth a short comment.
I added one.
Thanks. I'd move it to the inner indentation level so it's closer to
the test at issue.
I would also suggest reordering the documentation and the
postgresql.conf.sample file so that reserved_connections precedes
superuser_reserved_connections, instead of following it.
Other than that, this seems like it might be about ready to commit,
barring objections or bug reports.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jan 19, 2023 at 02:17:35PM -0500, Robert Haas wrote:
On Thu, Jan 19, 2023 at 12:54 PM Nathan Bossart
<nathandbossart@gmail.com> wrote:OK. Might be worth a short comment.
I added one.
Thanks. I'd move it to the inner indentation level so it's closer to
the test at issue.
I meant for it to cover the call to HaveNFreeProcs() as well since the same
idea applies. I left it the same for now, but if you still think it makes
sense to move it, I'll do so.
I would also suggest reordering the documentation and the
postgresql.conf.sample file so that reserved_connections precedes
superuser_reserved_connections, instead of following it.
Makes sense.
Other than that, this seems like it might be about ready to commit,
barring objections or bug reports.
Awesome.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v5-0001-Code-review-for-ea92368.patchtext/x-diff; charset=us-asciiDownload
From a6811b643df94c9057373fd687398c85a807fd5e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 18 Jan 2023 12:43:41 -0800
Subject: [PATCH v5 1/3] Code review for ea92368.
This commit missed an error message and a line in the docs.
---
doc/src/sgml/config.sgml | 3 +--
src/backend/utils/init/postinit.c | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89d53f2a64..e019a1aac9 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -725,8 +725,7 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
- connections will be accepted only for superusers, and no
- new replication connections will be accepted.
+ connections will be accepted only for superusers.
</para>
<para>
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index ae5a85ed65..9145d96b38 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -931,7 +931,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
!HaveNFreeProcs(ReservedBackends))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for non-replication superuser connections")));
+ errmsg("remaining connection slots are reserved for superusers")));
/* Check replication permissions needed for walsender processes. */
if (am_walsender)
--
2.25.1
v5-0002-Rename-ReservedBackends-to-SuperuserReservedConne.patchtext/x-diff; charset=us-asciiDownload
From e0390f0120315746ea04a9fa1bf709def76e6196 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 13:58:56 -0800
Subject: [PATCH v5 2/3] Rename ReservedBackends to
SuperuserReservedConnections.
This is in preparation for adding a new reserved_connections GUC.
---
src/backend/postmaster/postmaster.c | 20 ++++++++++----------
src/backend/utils/init/postinit.c | 4 ++--
src/backend/utils/misc/guc_tables.c | 2 +-
src/include/postmaster/postmaster.h | 2 +-
4 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9cedc1b9f0..3f799c4ac8 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -204,15 +204,15 @@ char *Unix_socket_directories;
char *ListenAddresses;
/*
- * ReservedBackends is the number of backends reserved for superuser use.
- * This number is taken out of the pool size given by MaxConnections so
- * number of backend slots available to non-superusers is
- * (MaxConnections - ReservedBackends). Note what this really means is
- * "if there are <= ReservedBackends connections available, only superusers
- * can make new connections" --- pre-existing superuser connections don't
- * count against the limit.
+ * SuperuserReservedConnections is the number of backends reserved for
+ * superuser use. This number is taken out of the pool size given by
+ * MaxConnections so number of backend slots available to non-superusers is
+ * (MaxConnections - SuperuserReservedConnections). Note what this really
+ * means is "if there are <= SuperuserReservedConnections connections
+ * available, only superusers can make new connections" --- pre-existing
+ * superuser connections don't count against the limit.
*/
-int ReservedBackends;
+int SuperuserReservedConnections;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +908,11 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (ReservedBackends >= MaxConnections)
+ if (SuperuserReservedConnections >= MaxConnections)
{
write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- ReservedBackends, MaxConnections);
+ SuperuserReservedConnections, MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 9145d96b38..40f145e0ab 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -927,8 +927,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
* limited by max_connections or superuser_reserved_connections.
*/
if (!am_superuser && !am_walsender &&
- ReservedBackends > 0 &&
- !HaveNFreeProcs(ReservedBackends))
+ SuperuserReservedConnections > 0 &&
+ !HaveNFreeProcs(SuperuserReservedConnections))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("remaining connection slots are reserved for superusers")));
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index cd0fc2cb8f..0fa9fdd3c5 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2163,7 +2163,7 @@ struct config_int ConfigureNamesInt[] =
gettext_noop("Sets the number of connection slots reserved for superusers."),
NULL
},
- &ReservedBackends,
+ &SuperuserReservedConnections,
3, 0, MAX_BACKENDS,
NULL, NULL, NULL
},
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 203177e1ff..0e4b8ded34 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -15,7 +15,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
-extern PGDLLIMPORT int ReservedBackends;
+extern PGDLLIMPORT int SuperuserReservedConnections;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
--
2.25.1
v5-0003-Introduce-reserved_connections-and-pg_use_reserve.patchtext/x-diff; charset=us-asciiDownload
From 107d753e1eead744edd960b255bcf1b29ef41514 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 17 Jan 2023 15:36:59 -0800
Subject: [PATCH v5 3/3] Introduce reserved_connections and
pg_use_reserved_connections.
This provides a way to reserve connection slots for non-superusers.
superuser_reserved_connections remains as a final reserve in case
reserved_connections has been exhausted.
---
doc/src/sgml/config.sgml | 39 ++++++++++++++++++-
doc/src/sgml/user-manag.sgml | 5 +++
src/backend/postmaster/postmaster.c | 29 +++++++++-----
src/backend/storage/lmgr/proc.c | 16 +++++---
src/backend/utils/init/postinit.c | 31 +++++++++++----
src/backend/utils/misc/guc_tables.c | 11 ++++++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_authid.dat | 5 +++
src/include/postmaster/postmaster.h | 1 +
src/include/storage/proc.h | 2 +-
10 files changed, 115 insertions(+), 25 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e019a1aac9..dc9b78b0b7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -708,6 +708,37 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-reserved-connections" xreflabel="reserved_connections">
+ <term><varname>reserved_connections</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>reserved_connections</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Determines the number of connection <quote>slots</quote> that are
+ reserved for connections by roles with privileges of the
+ <link linkend="predefined-roles-table"><literal>pg_used_reserved_connections</literal></link>
+ role. Whenever the number of free connection slots is greater than
+ <xref linkend="guc-superuser-reserved-connections"/> but less than or
+ equal to the sum of <varname>superuser_reserved_connections</varname>
+ and <varname>reserved_connections</varname>, new connections will be
+ accepted only for superusers and roles with privileges of
+ <literal>pg_use_reserved_connections</literal>. If
+ <varname>superuser_reserved_connections</varname> or fewer connection
+ slots are available, new connections will be accepted only for
+ superusers.
+ </para>
+
+ <para>
+ The default value is zero connections. The value must be less than
+ <varname>max_connections</varname> minus
+ <varname>superuser_reserved_connections</varname>. This parameter can
+ only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-superuser-reserved-connections"
xreflabel="superuser_reserved_connections">
<term><varname>superuser_reserved_connections</varname>
@@ -725,12 +756,16 @@ include_dir 'conf.d'
number of active concurrent connections is at least
<varname>max_connections</varname> minus
<varname>superuser_reserved_connections</varname>, new
- connections will be accepted only for superusers.
+ connections will be accepted only for superusers. The connection slots
+ reserved by this parameter are intended as final reserve for emergency
+ use after the slots reserved by
+ <xref linkend="guc-reserved-connections"/> have been exhausted.
</para>
<para>
The default value is three connections. The value must be less
- than <varname>max_connections</varname>.
+ than <varname>max_connections</varname> minus
+ <varname>reserved_connections</varname>.
This parameter can only be set at server start.
</para>
</listitem>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 71a2d8f298..002c1e3aff 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -689,6 +689,11 @@ DROP ROLE doomed_role;
and <link linkend="sql-lock"><command>LOCK TABLE</command></link> on all
relations.</entry>
</row>
+ <row>
+ <entry>pg_use_reserved_connections</entry>
+ <entry>Allow use of connection slots reserved via
+ <xref linkend="guc-reserved-connections"/>.</entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3f799c4ac8..aca1ef91b5 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -205,14 +205,24 @@ char *ListenAddresses;
/*
* SuperuserReservedConnections is the number of backends reserved for
- * superuser use. This number is taken out of the pool size given by
- * MaxConnections so number of backend slots available to non-superusers is
- * (MaxConnections - SuperuserReservedConnections). Note what this really
- * means is "if there are <= SuperuserReservedConnections connections
- * available, only superusers can make new connections" --- pre-existing
- * superuser connections don't count against the limit.
+ * superuser use, and ReservedConnections is the number of backends reserved
+ * for use by roles with privileges of the pg_use_reserved_connections
+ * predefined role. These are taken out of the pool of MaxConnections backend
+ * slots, so the number of backend slots available for roles that are neither
+ * superuser nor have privileges of pg_use_reserved_connections is
+ * (MaxConnections - SuperuserReservedConnections - ReservedConnections).
+ *
+ * If the number of remaining slots is less than or equal to
+ * SuperuserReservedConnections, only superusers can make new connections. If
+ * the number of remaining slots is greater than SuperuserReservedConnections
+ * but less than or equal to
+ * (SuperuserReservedConnections + ReservedConnections), only superusers and
+ * roles with privileges of pg_use_reserved_connections can make new
+ * connections. Note that pre-existing superuser and
+ * pg_use_reserved_connections connections don't count against the limits.
*/
int SuperuserReservedConnections;
+int ReservedConnections;
/* The socket(s) we're listening to. */
#define MAXLISTEN 64
@@ -908,11 +918,12 @@ PostmasterMain(int argc, char *argv[])
/*
* Check for invalid combinations of GUC settings.
*/
- if (SuperuserReservedConnections >= MaxConnections)
+ if (SuperuserReservedConnections + ReservedConnections >= MaxConnections)
{
- write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
+ write_stderr("%s: superuser_reserved_connections (%d) plus reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
- SuperuserReservedConnections, MaxConnections);
+ SuperuserReservedConnections, ReservedConnections,
+ MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index f8ac4edd6f..22b4278610 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -645,27 +645,33 @@ GetStartupBufferPinWaitBufId(void)
}
/*
- * Check whether there are at least N free PGPROC objects.
+ * Check whether there are at least N free PGPROC objects. If false is
+ * returned, *nfree will be set to the number of free PGPROC objects.
+ * Otherwise, *nfree will be set to n.
*
* Note: this is designed on the assumption that N will generally be small.
*/
bool
-HaveNFreeProcs(int n)
+HaveNFreeProcs(int n, int *nfree)
{
dlist_iter iter;
+ Assert(n > 0);
+ Assert(nfree);
+
SpinLockAcquire(ProcStructLock);
+ *nfree = 0;
dlist_foreach(iter, &ProcGlobal->freeProcs)
{
- n--;
- if (n == 0)
+ (*nfree)++;
+ if (*nfree == n)
break;
}
SpinLockRelease(ProcStructLock);
- return (n <= 0);
+ return (*nfree == n);
}
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 40f145e0ab..2f07ca7a0e 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -719,6 +719,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
bool am_superuser;
char *fullpath;
char dbname[NAMEDATALEN];
+ int nfree = 0;
elog(DEBUG3, "InitPostgres");
@@ -922,16 +923,30 @@ InitPostgres(const char *in_dbname, Oid dboid,
}
/*
- * The last few connection slots are reserved for superusers. Replication
- * connections are drawn from slots reserved with max_wal_senders and not
- * limited by max_connections or superuser_reserved_connections.
+ * The last few connection slots are reserved for superusers and roles with
+ * privileges of pg_use_reserved_connections. Replication connections are
+ * drawn from slots reserved with max_wal_senders and are not limited by
+ * max_connections, superuser_reserved_connections, or
+ * reserved_connections.
+ *
+ * Note: At this point, the new backend has already claimed a proc struct,
+ * so we must check whether the number of free slots is strictly less than
+ * the reserved connection limits.
*/
if (!am_superuser && !am_walsender &&
- SuperuserReservedConnections > 0 &&
- !HaveNFreeProcs(SuperuserReservedConnections))
- ereport(FATAL,
- (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for superusers")));
+ (SuperuserReservedConnections + ReservedConnections) > 0 &&
+ !HaveNFreeProcs(SuperuserReservedConnections + ReservedConnections, &nfree))
+ {
+ if (nfree < SuperuserReservedConnections)
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for superusers")));
+
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
+ ereport(FATAL,
+ (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+ errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_connections")));
+ }
/* Check replication permissions needed for walsender processes. */
if (am_walsender)
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0fa9fdd3c5..e1753a40fa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2168,6 +2168,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"reserved_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+ gettext_noop("Sets the number of connection slots reserved for roles "
+ "with privileges of pg_use_reserved_connections."),
+ NULL
+ },
+ &ReservedConnections,
+ 0, 0, MAX_BACKENDS,
+ NULL, NULL, NULL
+ },
+
{
{"min_dynamic_shared_memory", PGC_POSTMASTER, RESOURCES_MEM,
gettext_noop("Amount of dynamic shared memory reserved at startup."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..d06074b86f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -63,6 +63,7 @@
# (change requires restart)
#port = 5432 # (change requires restart)
#max_connections = 100 # (change requires restart)
+#reserved_connections = 0 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
# (change requires restart)
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 2a2fee7d28..f2e5663c9f 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -89,5 +89,10 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
+ rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' },
]
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 0e4b8ded34..3b3889c58c 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -16,6 +16,7 @@
/* GUC options */
extern PGDLLIMPORT bool EnableSSL;
extern PGDLLIMPORT int SuperuserReservedConnections;
+extern PGDLLIMPORT int ReservedConnections;
extern PGDLLIMPORT int PostPortNumber;
extern PGDLLIMPORT int Unix_socket_permissions;
extern PGDLLIMPORT char *Unix_socket_group;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index dd45b8ee9b..4258cd92c9 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -445,7 +445,7 @@ extern void InitAuxiliaryProcess(void);
extern void SetStartupBufferPinWaitBufId(int bufid);
extern int GetStartupBufferPinWaitBufId(void);
-extern bool HaveNFreeProcs(int n);
+extern bool HaveNFreeProcs(int n, int *nfree);
extern void ProcReleaseLocks(bool isCommit);
extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable);
--
2.25.1
On Thu, Jan 19, 2023 at 2:46 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
Thanks. I'd move it to the inner indentation level so it's closer to
the test at issue.I meant for it to cover the call to HaveNFreeProcs() as well since the same
idea applies. I left it the same for now, but if you still think it makes
sense to move it, I'll do so.
Hmm, OK. If you want to leave it where it is, I won't argue further.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 1/19/23 6:28 PM, tushar wrote:
There is� one typo , for the doc changes, it is� mentioned
"pg_use_reserved_backends" but i think it supposed to be
"pg_use_reserved_connections"
under Table 22.1. Predefined Roles.
Thanks, this is fixed now with the latest patches.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On Fri, Jan 20, 2023 at 07:04:58PM +0530, tushar wrote:
On 1/19/23 6:28 PM, tushar wrote:
There is� one typo , for the doc changes, it is� mentioned
"pg_use_reserved_backends" but i think it supposed to be
"pg_use_reserved_connections"
under Table 22.1. Predefined Roles.Thanks, this is fixed now with the latest patches.
Thank you for reviewing.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jan 20, 2023 at 1:10 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
Thanks, this is fixed now with the latest patches.
Thank you for reviewing.
Thanks to you both. I have committed these patches.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Fri, Jan 20, 2023 at 03:42:03PM -0500, Robert Haas wrote:
Thanks to you both. I have committed these patches.
Thanks! Does this need a catversion bump?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jan 20, 2023 at 4:02 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Fri, Jan 20, 2023 at 03:42:03PM -0500, Robert Haas wrote:
Thanks to you both. I have committed these patches.
Thanks! Does this need a catversion bump?
I was surprised by this question because I thought I'd included one.
But it turns out I didn't include that in the commit and it's still in
my working tree. *facepalm*
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jan 19, 2023 at 8:34 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Jan 19, 2023 at 6:15 AM tushar <tushar.ahuja@enterprisedb.com>
wrote:postgres=# create role fff with createrole;
CREATE ROLE
postgres=# create role xxx;
CREATE ROLE
postgres=# set role fff;
SET
postgres=> alter role xxx with createrole;
ERROR: permission denied
postgres=>Here fff would need ADMIN OPTION on xxx to be able to make modifications
to it.
Thanks, Robert, that was helpful.
Please refer to this scenario where I am able to give createrole privileges
but not replication privilege to role
postgres=# create role t1 createrole;
CREATE ROLE
postgres=# create role t2 replication;
CREATE ROLE
postgres=# create role t3;
CREATE ROLE
postgres=# grant t3 to t1,t2 with admin option;
GRANT ROLE
postgres=# set session authorization t1;
SET
*postgres=> alter role t3 createrole ;ALTER ROLE*
postgres=> set session authorization t2;
SET
postgres=> alter role t3 replication;
ERROR: permission denied
This same behavior was observed in v14 as well but why i am able to give
createrole grant but not replication?
regards,
On Mon, Jan 23, 2023 at 10:25 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
Please refer to this scenario where I am able to give createrole privileges but not replication privilege to role
postgres=# create role t1 createrole;
CREATE ROLE
postgres=# create role t2 replication;
CREATE ROLE
postgres=# create role t3;
CREATE ROLE
postgres=# grant t3 to t1,t2 with admin option;
GRANT ROLE
postgres=# set session authorization t1;
SET
postgres=> alter role t3 createrole ;
ALTER ROLE
postgres=> set session authorization t2;
SET
postgres=> alter role t3 replication;
ERROR: permission deniedThis same behavior was observed in v14 as well but why i am able to give createrole grant but not replication?
In previous releases, you needed to have CREATEROLE in order to be
able to perform user management functions. In master, you still need
CREATEROLE, and you also need ADMIN OPTION on the role. In this
scenario, only t1 meets those requirements with respect to t3, so only
t1 can manage t3. t2 can SET ROLE to t3 and grant membership in t3,
but it can't set role properties on t3 or change t3's password or
things like that, because the ability to make user management changes
is controlled by CREATEROLE.
The patch is only intended to change behavior in the case where you
possess both CREATEROLE and also ADMIN OPTION on the target role (but
not SUPERUSER). In that scenario, it intends to change whether you can
give or remove the CREATEDB, REPLICATION, and BYPASSRLS properties
from a user.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Jan 23, 2023 at 10:28 PM Robert Haas <robertmhaas@gmail.com> wrote:
In previous releases, you needed to have CREATEROLE in order to be
able to perform user management functions. In master, you still need
CREATEROLE, and you also need ADMIN OPTION on the role. In this
scenario, only t1 meets those requirements with respect to t3, so only
t1 can manage t3. t2 can SET ROLE to t3 and grant membership in t3,
but it can't set role properties on t3 or change t3's password or
things like that, because the ability to make user management changes
is controlled by CREATEROLE.
ok.
The patch is only intended to change behavior in the case where you
possess both CREATEROLE and also ADMIN OPTION on the target role (but
not SUPERUSER). In that scenario, it intends to change whether you can
give or remove the CREATEDB, REPLICATION, and BYPASSRLS properties
from a user.
right, Neha/I have tested with different scenarios using
createdb/replication/bypassrls and other
privileges properties on the role. also checked
pg_dumpall/pg_basebackup and everything looks fine.
regards,
On Tue, Jan 24, 2023 at 9:07 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
right, Neha/I have tested with different scenarios using createdb/replication/bypassrls and other
privileges properties on the role. also checked pg_dumpall/pg_basebackup and everything looks fine.
Thanks. I have committed the patch.
--
Robert Haas
EDB: http://www.enterprisedb.com
moving this discussion to a new thread...
On Thu, Jan 19, 2023 at 10:20:33AM -0500, Robert Haas wrote:
On Wed, Jan 18, 2023 at 6:17 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
However, as the attribute
system becomes more sophisticated, I think we ought to improve the error
messages in user.c. IMHO messages like "permission denied" could be
greatly improved with some added context.This probably shouldn't block your patch, but I think it's worth doing in
v16 since there are other changes in this area. I'm happy to help.That would be great. I agree that it's good to try to improve the
error messages. It hasn't been entirely clear to me how to do that.
For instance, I don't think we want to say something like:ERROR: must have CREATEROLE privilege and ADMIN OPTION on the target
role, or in lieu of both of those to be superuser, to set the
CONNECTION LIMIT for another role
ERROR: must have CREATEROLE privilege and ADMIN OPTION on the target
role, plus also CREATEDB, or in lieu of all that to be superuser, to
remove the CREATEDB property from another roleSuch messages are long and we'd end up with a lot of variants. It's
possible that the messages could be multi-tier. For instance, if we
determine that you're trying to manage users and you don't have
permission to manage ANY user, we could say:ERROR: permission to manage roles denied
DETAIL: You must have the CREATEROLE privilege or be a superuser to
manage roles.If you could potentially manage some user, but not the one you're
trying to manage, we could say:ERROR: permission to manage role "%s" denied
DETAIL: You need ADMIN OPTION on the target role to manage it.If you have permission to manage the target role but not in the
requested manner, we could then say something like:ERROR: permission to manage CREATEDB for role "%s" denied
DETAIL: You need CREATEDB to manage it.This is just one idea, and maybe not the best one. I'm just trying to
say that I think this is basically an organizational problem. We need
a plan for how we're going to report errors that is not too
complicated to implement with reasonable effort, and that will produce
messages that users will understand. I'd be delighted if you wanted to
provide either ideas or patches...
Here is an early draft of some modest improvements to the user.c error
messages. I basically just tried to standardize the style of and add
context to the existing error messages. I used errhint() for this extra
context, but errdetail() would work, too. This isn't perfect. You might
still have to go through a couple rounds of errors before your role has all
the privileges it needs for a command, but this seems to improve matters a
little.
I think there is still a lot of room for improvement, but I wanted to at
least get the discussion started before I went too far.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
enhance_error_messages.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 3a92e930c0..1982831e2a 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -316,23 +316,28 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("permission denied to create role"),
+ errhint("You must have CREATEROLE privilege to create roles.")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create superusers")));
+ errmsg("permission denied to create role"),
+ errhint("You must have SUPERUSER privilege to create roles with SUPERUSER.")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb permission to create createdb users")));
+ errmsg("permission denied to create role"),
+ errhint("You must have CREATEDB privilege to create roles with CREATEDB.")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication permission to create replication users")));
+ errmsg("permission denied to create role"),
+ errhint("You must have REPLICATION privilege to create roles with REPLICATION.")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls to create bypassrls users")));
+ errmsg("permission denied to create role"),
+ errhint("You must have BYPASSRLS privilege to create roles with BYPASSRLS.")));
}
/*
@@ -747,7 +752,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!superuser() && (authform->rolsuper || dissuper))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
+ errmsg("permission denied to alter role"),
+ errhint("You must have SUPERUSER privilege to alter roles with SUPERUSER.")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -758,16 +764,13 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
/* things an unprivileged user certainly can't do */
if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
- dvalidUntil || disreplication || dbypassRLS)
+ dvalidUntil || disreplication || dbypassRLS ||
+ (dpassword && roleid != currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
-
- /* an unprivileged user can change their own password */
- if (dpassword && roleid != currentUserId)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errhint("You must have CREATEROLE privilege and ADMIN OPTION on role \"%s\".",
+ rolename)));
}
else if (!superuser())
{
@@ -779,23 +782,27 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb privilege to change createdb attribute")));
+ errmsg("permission denied to alter role"),
+ errhint("You must have CREATEDB privilege to alter roles with CREATEDB.")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication privilege to change replication attribute")));
+ errmsg("permission denied to alter role"),
+ errhint("You must have REPLICATION privilege to alter roles with REPLICATION.")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ errmsg("permission denied to alter role"),
+ errhint("You must have BYPASSRLS privilege to alter roles with BYPASSRLS.")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\" to add members",
- rolename)));
+ errmsg("permission denied to alter role"),
+ errhint("You must have ADMIN OPTION on role \"%s\" to add members.",
+ rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
@@ -838,7 +845,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: bootstrap user must be superuser")));
+ errmsg("permission denied to alter role"),
+ errhint("The bootstrap user must be superuser.")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
@@ -999,7 +1007,8 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ errmsg("permission denied to alter role"),
+ errhint("You must have SUPERUSER privilege to alter roles with SUPERUSER.")));
}
else
{
@@ -1008,7 +1017,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errhint("You must have CREATEROLE privilege and ADMIN OPTION on role \"%s\".",
+ NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
@@ -1038,7 +1049,8 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter settings globally")));
+ errmsg("permission denied to alter setting"),
+ errhint("You must have SUPERUSER privilege to alter settings globally.")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1061,7 +1073,8 @@ DropRole(DropRoleStmt *stmt)
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop role")));
+ errmsg("permission denied to drop role"),
+ errhint("You must have CREATEROLE privilege and ADMIN OPTION on the target roles.")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
@@ -1131,12 +1144,14 @@ DropRole(DropRoleStmt *stmt)
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to drop superusers")));
+ errmsg("permission denied to drop role"),
+ errhint("You must have SUPERUSER privilege to drop roles with SUPERUSER.")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errhint("You must have CREATEROLE privilege and ADMIN OPTION on role \"%s\".",
+ NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
@@ -1378,12 +1393,13 @@ RenameRole(const char *oldname, const char *newname)
* Only superusers can mess with superusers. Otherwise, a user with
* CREATEROLE can rename a role for which they have ADMIN OPTION.
*/
- if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+ if (authform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to rename superusers")));
+ errmsg("permission denied to rename role"),
+ errhint("You must have SUPERUSER privilege to rename roles with SUPERUSER.")));
}
else
{
@@ -1391,7 +1407,9 @@ RenameRole(const char *oldname, const char *newname)
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to rename role")));
+ errmsg("permission denied to rename role"),
+ errhint("You must have CREATEROLE privilege and ADMIN OPTION on role \"%s\".",
+ NameStr(authform->rolname))));
}
/* OK, construct the modified tuple */
@@ -1554,7 +1572,9 @@ DropOwnedObjects(DropOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop objects")));
+ errmsg("permission denied to drop objects"),
+ errhint("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Ok, do it */
@@ -1581,7 +1601,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errhint("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
@@ -1590,7 +1612,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errhint("You must have privileges of role \"%s\".",
+ GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
@@ -1738,7 +1762,7 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("ADMIN OPTION cannot be granted back to your own grantor")));
plan_member_revoke(memlist, actions, memberid);
}
@@ -1763,7 +1787,7 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("ADMIN OPTION cannot be granted back to your own grantor")));
ReleaseSysCacheList(memlist);
}
@@ -2081,9 +2105,20 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errhint("You must have SUPERUSER privilege to grant roles with SUPERUSER.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errhint("You must have SUPERUSER privilege to revoke roles with SUPERUSER.")));
+ }
}
else
{
@@ -2091,10 +2126,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(roleid, false))));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errhint("You must have ADMIN OPTION on role \"%s\".",
+ GetUserNameFromId(roleid, false))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errhint("You must have ADMIN OPTION on role \"%s\".",
+ GetUserNameFromId(roleid, false))));
+ }
}
}
@@ -2173,14 +2220,18 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errhint("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("grantor must have ADMIN OPTION on \"%s\"",
- GetUserNameFromId(roleid, false))));
+ errmsg("permission denied to grant privileges as role \"%s\"",
+ GetUserNameFromId(grantorId, false)),
+ errhint("The grantor must have ADMIN OPTION on role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
else
{
@@ -2188,7 +2239,9 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errhint("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
}
/*
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index c8beb36bab..7549873ee5 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -7,26 +7,35 @@ CREATE ROLE regress_role_normal;
-- fail, CREATEROLE user can't give away role attributes without having them
SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
-ERROR: must be superuser to create superusers
+ERROR: permission denied to create role
+HINT: You must have SUPERUSER privilege to create roles with SUPERUSER.
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+HINT: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+HINT: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must have bypassrls to create bypassrls users
+ERROR: permission denied to create role
+HINT: You must have BYPASSRLS privilege to create roles with BYPASSRLS.
CREATE ROLE regress_nosuch_createdb CREATEDB;
-ERROR: must have createdb permission to create createdb users
+ERROR: permission denied to create role
+HINT: You must have CREATEDB privilege to create roles with CREATEDB.
-- ok, can create a role without any special attributes
CREATE ROLE regress_role_limited;
-- fail, can't give it in any of the restricted attributes
ALTER ROLE regress_role_limited SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+HINT: You must have SUPERUSER privilege to alter roles with SUPERUSER.
ALTER ROLE regress_role_limited REPLICATION;
-ERROR: must have replication privilege to change replication attribute
+ERROR: permission denied to alter role
+HINT: You must have REPLICATION privilege to alter roles with REPLICATION.
ALTER ROLE regress_role_limited CREATEDB;
-ERROR: must have createdb privilege to change createdb attribute
+ERROR: permission denied to alter role
+HINT: You must have CREATEDB privilege to alter roles with CREATEDB.
ALTER ROLE regress_role_limited BYPASSRLS;
-ERROR: must have bypassrls privilege to change bypassrls attribute
+ERROR: permission denied to alter role
+HINT: You must have BYPASSRLS privilege to alter roles with BYPASSRLS.
DROP ROLE regress_role_limited;
-- ok, can give away these role attributes if you have them
SET SESSION AUTHORIZATION regress_role_admin;
@@ -43,9 +52,11 @@ ALTER ROLE regress_createdb NOCREATEDB;
ALTER ROLE regress_createdb CREATEDB;
-- fail, can't toggle SUPERUSER
ALTER ROLE regress_createdb SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+HINT: You must have SUPERUSER privilege to alter roles with SUPERUSER.
ALTER ROLE regress_createdb NOSUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+HINT: You must have SUPERUSER privilege to alter roles with SUPERUSER.
-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
@@ -59,7 +70,8 @@ CREATE ROLE regress_noiseword SYSID 12345;
NOTICE: SYSID can no longer be specified
-- fail, cannot grant membership in superuser role
CREATE ROLE regress_nosuch_super IN ROLE regress_role_super;
-ERROR: must be superuser to alter superusers
+ERROR: permission denied to grant role "regress_role_super"
+HINT: You must have SUPERUSER privilege to grant roles with SUPERUSER.
-- fail, database owner cannot have members
CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner;
ERROR: role "pg_database_owner" cannot have explicit members
@@ -97,8 +109,10 @@ COMMENT ON ROLE regress_role_normal IS 'some comment';
ERROR: must have admin option on role "regress_role_normal"
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
+HINT: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
ALTER ROLE regress_role_normal NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-ERROR: permission denied
+ERROR: permission denied to alter role
+HINT: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
@@ -123,6 +137,7 @@ ERROR: must be able to SET ROLE "regress_tenant"
-- fail, we don't inherit permissions from regress_tenant
REASSIGN OWNED BY regress_tenant TO regress_createrole;
ERROR: permission denied to reassign objects
+HINT: You must have privileges of role "regress_tenant".
-- ok, create a role with a value for createrole_self_grant
SET createrole_self_grant = 'set, inherit';
CREATE ROLE regress_tenant2;
@@ -150,25 +165,35 @@ ERROR: must be able to SET ROLE "regress_tenant2"
DROP TABLE tenant2_table;
-- fail, CREATEROLE is not enough to create roles in privileged roles
CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data;
-ERROR: must have admin option on role "pg_read_all_data"
+ERROR: permission denied to grant role "pg_read_all_data"
+HINT: You must have ADMIN OPTION on role "pg_read_all_data".
CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data;
-ERROR: must have admin option on role "pg_write_all_data"
+ERROR: permission denied to grant role "pg_write_all_data"
+HINT: You must have ADMIN OPTION on role "pg_write_all_data".
CREATE ROLE regress_monitor IN ROLE pg_monitor;
-ERROR: must have admin option on role "pg_monitor"
+ERROR: permission denied to grant role "pg_monitor"
+HINT: You must have ADMIN OPTION on role "pg_monitor".
CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings;
-ERROR: must have admin option on role "pg_read_all_settings"
+ERROR: permission denied to grant role "pg_read_all_settings"
+HINT: You must have ADMIN OPTION on role "pg_read_all_settings".
CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats;
-ERROR: must have admin option on role "pg_read_all_stats"
+ERROR: permission denied to grant role "pg_read_all_stats"
+HINT: You must have ADMIN OPTION on role "pg_read_all_stats".
CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables;
-ERROR: must have admin option on role "pg_stat_scan_tables"
+ERROR: permission denied to grant role "pg_stat_scan_tables"
+HINT: You must have ADMIN OPTION on role "pg_stat_scan_tables".
CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files;
-ERROR: must have admin option on role "pg_read_server_files"
+ERROR: permission denied to grant role "pg_read_server_files"
+HINT: You must have ADMIN OPTION on role "pg_read_server_files".
CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files;
-ERROR: must have admin option on role "pg_write_server_files"
+ERROR: permission denied to grant role "pg_write_server_files"
+HINT: You must have ADMIN OPTION on role "pg_write_server_files".
CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program;
-ERROR: must have admin option on role "pg_execute_server_program"
+ERROR: permission denied to grant role "pg_execute_server_program"
+HINT: You must have ADMIN OPTION on role "pg_execute_server_program".
CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend;
-ERROR: must have admin option on role "pg_signal_backend"
+ERROR: permission denied to grant role "pg_signal_backend"
+HINT: You must have ADMIN OPTION on role "pg_signal_backend".
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
@@ -211,11 +236,13 @@ DROP ROLE regress_inroles;
DROP ROLE regress_adminroles;
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
DROP ROLE regress_role_super;
-ERROR: must be superuser to drop superusers
+ERROR: permission denied to drop role
+HINT: You must have SUPERUSER privilege to drop roles with SUPERUSER.
DROP ROLE regress_role_admin;
ERROR: current user cannot be dropped
DROP ROLE regress_rolecreator;
-ERROR: must have admin option on role "regress_rolecreator"
+ERROR: permission denied to drop role
+HINT: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_rolecreator".
-- ok
RESET SESSION AUTHORIZATION;
REVOKE CREATE ON DATABASE regression FROM regress_role_admin CASCADE;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 520035f6a0..0556068e32 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -48,12 +48,16 @@ SET SESSION AUTHORIZATION regress_dep_user0;
-- permission denied
DROP OWNED BY regress_dep_user1;
ERROR: permission denied to drop objects
+HINT: You must have privileges of role "regress_dep_user1".
DROP OWNED BY regress_dep_user0, regress_dep_user2;
ERROR: permission denied to drop objects
+HINT: You must have privileges of role "regress_dep_user2".
REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
ERROR: permission denied to reassign objects
+HINT: You must have privileges of role "regress_dep_user1".
REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
ERROR: permission denied to reassign objects
+HINT: You must have privileges of role "regress_dep_user1".
-- this one is allowed
DROP OWNED BY regress_dep_user0;
CREATE TABLE deptest1 (f1 int unique);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..e0f5a486b9 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -37,7 +37,7 @@ CREATE ROLE regress_priv_role;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3;
-ERROR: admin option cannot be granted back to your own grantor
+ERROR: ADMIN OPTION cannot be granted back to your own grantor
-- need CASCADE to revoke grant or admin option if dependent grants exist
REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail
ERROR: dependent privileges exist
@@ -156,7 +156,8 @@ ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate
NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1"
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted
-ERROR: must have CREATEROLE privilege to change another user's password
+ERROR: permission denied to alter role
+HINT: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_priv_user2".
RESET SESSION AUTHORIZATION;
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1;
@@ -168,7 +169,8 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1;
-- test owner privileges
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION
-ERROR: grantor must have ADMIN OPTION on "regress_priv_role"
+ERROR: permission denied to grant privileges as role "regress_priv_role"
+HINT: The grantor must have ADMIN OPTION on role "regress_priv_role".
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE;
REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error
ERROR: role "foo" does not exist
@@ -1795,7 +1797,8 @@ REFRESH MATERIALIZED VIEW sro_mv;
ERROR: cannot fire deferred trigger within security-restricted operation
CONTEXT: SQL function "mv_action" statement 1
BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+HINT: You must have ADMIN OPTION on role "regress_priv_group2".
CONTEXT: SQL function "unwanted_grant" statement 1
SQL statement "SELECT unwanted_grant()"
PL/pgSQL function sro_trojan() line 1 at PERFORM
@@ -1825,10 +1828,12 @@ CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+HINT: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+HINT: You must have ADMIN OPTION on role "regress_priv_group2".
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4"
dogrant_ok
@@ -1838,10 +1843,12 @@ NOTICE: role "regress_priv_user5" has already been granted membership in role "
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+HINT: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+HINT: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user4;
DROP FUNCTION dogrant_ok();
REVOKE regress_priv_group2 FROM regress_priv_user5;
Nathan Bossart <nathandbossart@gmail.com> writes:
On Thu, Jan 19, 2023 at 10:20:33AM -0500, Robert Haas wrote:
That would be great. I agree that it's good to try to improve the
error messages. It hasn't been entirely clear to me how to do that.
For instance, I don't think we want to say something like:ERROR: must have CREATEROLE privilege and ADMIN OPTION on the target
role, or in lieu of both of those to be superuser, to set the
CONNECTION LIMIT for another role
ERROR: must have CREATEROLE privilege and ADMIN OPTION on the target
role, plus also CREATEDB, or in lieu of all that to be superuser, to
remove the CREATEDB property from another role
Here is an early draft of some modest improvements to the user.c error
messages. I basically just tried to standardize the style of and add
context to the existing error messages. I used errhint() for this extra
context, but errdetail() would work, too.
Yeah, I think the right fix is to keep the primary message pretty terse
and add detail in secondary messages. IMO most of these are errdetail not
errhint, because they are factual details about the rules [1]https://www.postgresql.org/docs/devel/error-style-guide.html. But other
than that quibble, Nathan's draft looked pretty good in a quick once-over.
regards, tom lane
[1]: https://www.postgresql.org/docs/devel/error-style-guide.html
Please use
errdetail("You must have %s privilege to create roles with %s.",
"SUPERUSER", "SUPERUSER")));
in this kind of message where multiple copies appear that only differ in
the keyword to use, to avoid creating four copies of essentially the
same string.
This applies in several places.
- errmsg("must have createdb privilege to change createdb attribute"))); + errmsg("permission denied to alter role"), + errhint("You must have CREATEDB privilege to alter roles with CREATEDB.")));
I think this one is a bit ambiguous; does "with" mean that roles that
have that priv cannot be changed, or does it mean that you cannot meddle
with that bit in particular? I think it'd be better to say
"You must have %s privilege to change the %s attribute."
or something like that.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
Maybe there's lots of data loss but the records of data loss are also lost.
(Lincoln Yeoh)
Thanks for taking a look.
On Thu, Jan 26, 2023 at 10:07:39AM +0100, Alvaro Herrera wrote:
Please use
errdetail("You must have %s privilege to create roles with %s.",
"SUPERUSER", "SUPERUSER")));in this kind of message where multiple copies appear that only differ in
the keyword to use, to avoid creating four copies of essentially the
same string.This applies in several places.
I did this in v2.
- errmsg("must have createdb privilege to change createdb attribute"))); + errmsg("permission denied to alter role"), + errhint("You must have CREATEDB privilege to alter roles with CREATEDB.")));I think this one is a bit ambiguous; does "with" mean that roles that
have that priv cannot be changed, or does it mean that you cannot meddle
with that bit in particular? I think it'd be better to say
"You must have %s privilege to change the %s attribute."
or something like that.
Yeah, it's probably better to say "to alter roles with %s" to refer to
roles that presently have the attribute and "to change the %s attribute"
when referring to privileges for the attribute. I did this in v2, too.
I've also switched from errhint() to errdetail() as suggested by Tom.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v2-0001-Improve-user.c-error-messages.patchtext/x-diff; charset=us-asciiDownload
From a6db1f28f0e7079193af4fca3fde27cf4780dbb7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 26 Jan 2023 11:05:13 -0800
Subject: [PATCH v2 1/1] Improve user.c error messages.
---
src/backend/commands/user.c | 171 ++++++++++++++++------
src/test/regress/expected/create_role.out | 77 ++++++----
src/test/regress/expected/dependency.out | 4 +
src/test/regress/expected/privileges.out | 23 ++-
4 files changed, 195 insertions(+), 80 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 3a92e930c0..e2c80f5060 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -316,23 +316,33 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles.",
+ "CREATEROLE")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create superusers")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb permission to create createdb users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "CREATEDB", "CREATEDB")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication permission to create replication users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "REPLICATION", "REPLICATION")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls to create bypassrls users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/*
@@ -744,10 +754,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
roleid = authform->oid;
/* To mess with a superuser in any way you gotta be superuser. */
- if (!superuser() && (authform->rolsuper || dissuper))
+ if (!superuser() && authform->rolsuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ if (!superuser() && dissuper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "SUPERUSER", "SUPERUSER")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -758,16 +776,13 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
/* things an unprivileged user certainly can't do */
if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
- dvalidUntil || disreplication || dbypassRLS)
+ dvalidUntil || disreplication || dbypassRLS ||
+ (dpassword && roleid != currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
-
- /* an unprivileged user can change their own password */
- if (dpassword && roleid != currentUserId)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", rolename)));
}
else if (!superuser())
{
@@ -779,23 +794,30 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb privilege to change createdb attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "CREATEDB", "CREATEDB")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication privilege to change replication attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "REPLICATION", "REPLICATION")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\" to add members",
- rolename)));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s on role \"%s\" to add members.",
+ "ADMIN OPTION", rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
@@ -838,7 +860,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: bootstrap user must be superuser")));
+ errmsg("permission denied to alter role"),
+ errdetail("The bootstrap user must be superuser.")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
@@ -999,7 +1022,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1008,7 +1033,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATROLE", "ADMIN OPTION", NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
@@ -1038,7 +1065,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter settings globally")));
+ errmsg("permission denied to alter setting"),
+ errdetail("You must have %s privilege to alter settings globally.",
+ "SUPERUSER")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1061,7 +1090,9 @@ DropRole(DropRoleStmt *stmt)
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop role")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege and %s on the target roles.",
+ "CREATEROLE", "ADMIN OPTION")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
@@ -1131,12 +1162,15 @@ DropRole(DropRoleStmt *stmt)
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to drop superusers")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege to drop roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
@@ -1378,12 +1412,14 @@ RenameRole(const char *oldname, const char *newname)
* Only superusers can mess with superusers. Otherwise, a user with
* CREATEROLE can rename a role for which they have ADMIN OPTION.
*/
- if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+ if (authform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to rename superusers")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have %s privilege to rename roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1391,7 +1427,9 @@ RenameRole(const char *oldname, const char *newname)
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to rename role")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", NameStr(authform->rolname))));
}
/* OK, construct the modified tuple */
@@ -1554,7 +1592,9 @@ DropOwnedObjects(DropOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop objects")));
+ errmsg("permission denied to drop objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Ok, do it */
@@ -1581,7 +1621,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
@@ -1590,7 +1632,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
@@ -1738,7 +1782,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s cannot be granted back to your own grantor",
+ "ADMIN OPTION")));
plan_member_revoke(memlist, actions, memberid);
}
@@ -1763,7 +1808,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s cannot be granted back to your own grantor",
+ "ADMIN OPTION")));
ReleaseSysCacheList(memlist);
}
@@ -2081,9 +2127,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s privilege to grant roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s privilege to revoke roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ }
}
else
{
@@ -2091,10 +2150,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(roleid, false))));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
+ }
}
}
@@ -2173,14 +2244,18 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("grantor must have ADMIN OPTION on \"%s\"",
- GetUserNameFromId(roleid, false))));
+ errmsg("permission denied to grant privileges as role \"%s\"",
+ GetUserNameFromId(grantorId, false)),
+ errdetail("The grantor must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
}
else
{
@@ -2188,7 +2263,9 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
}
/*
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 9f431bd4f5..2a7d14dba9 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -7,26 +7,35 @@ CREATE ROLE regress_role_normal;
-- fail, CREATEROLE user can't give away role attributes without having them
SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
-ERROR: must be superuser to create superusers
+ERROR: permission denied to create role
+DETAIL: You must have SUPERUSER privilege to create roles with SUPERUSER.
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must have bypassrls to create bypassrls users
+ERROR: permission denied to create role
+DETAIL: You must have BYPASSRLS privilege to create roles with BYPASSRLS.
CREATE ROLE regress_nosuch_createdb CREATEDB;
-ERROR: must have createdb permission to create createdb users
+ERROR: permission denied to create role
+DETAIL: You must have CREATEDB privilege to create roles with CREATEDB.
-- ok, can create a role without any special attributes
CREATE ROLE regress_role_limited;
-- fail, can't give it in any of the restricted attributes
ALTER ROLE regress_role_limited SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
ALTER ROLE regress_role_limited REPLICATION;
-ERROR: must have replication privilege to change replication attribute
+ERROR: permission denied to alter role
+DETAIL: You must have REPLICATION privilege to change the REPLICATION attribute.
ALTER ROLE regress_role_limited CREATEDB;
-ERROR: must have createdb privilege to change createdb attribute
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEDB privilege to change the CREATEDB attribute.
ALTER ROLE regress_role_limited BYPASSRLS;
-ERROR: must have bypassrls privilege to change bypassrls attribute
+ERROR: permission denied to alter role
+DETAIL: You must have BYPASSRLS privilege to change the BYPASSRLS attribute.
DROP ROLE regress_role_limited;
-- ok, can give away these role attributes if you have them
SET SESSION AUTHORIZATION regress_role_admin;
@@ -43,9 +52,11 @@ ALTER ROLE regress_createdb NOCREATEDB;
ALTER ROLE regress_createdb CREATEDB;
-- fail, can't toggle SUPERUSER
ALTER ROLE regress_createdb SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
ALTER ROLE regress_createdb NOSUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
@@ -59,7 +70,8 @@ CREATE ROLE regress_noiseword SYSID 12345;
NOTICE: SYSID can no longer be specified
-- fail, cannot grant membership in superuser role
CREATE ROLE regress_nosuch_super IN ROLE regress_role_super;
-ERROR: must be superuser to alter superusers
+ERROR: permission denied to grant role "regress_role_super"
+DETAIL: You must have SUPERUSER privilege to grant roles with SUPERUSER.
-- fail, database owner cannot have members
CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner;
ERROR: role "pg_database_owner" cannot have explicit members
@@ -97,8 +109,10 @@ COMMENT ON ROLE regress_role_normal IS 'some comment';
ERROR: must have admin option on role "regress_role_normal"
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
ALTER ROLE regress_role_normal NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-ERROR: permission denied
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
@@ -123,6 +137,7 @@ ERROR: must be able to SET ROLE "regress_tenant"
-- fail, we don't inherit permissions from regress_tenant
REASSIGN OWNED BY regress_tenant TO regress_createrole;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_tenant".
-- ok, create a role with a value for createrole_self_grant
SET createrole_self_grant = 'set, inherit';
CREATE ROLE regress_tenant2;
@@ -150,25 +165,35 @@ ERROR: must be able to SET ROLE "regress_tenant2"
DROP TABLE tenant2_table;
-- fail, CREATEROLE is not enough to create roles in privileged roles
CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data;
-ERROR: must have admin option on role "pg_read_all_data"
+ERROR: permission denied to grant role "pg_read_all_data"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_data".
CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data;
-ERROR: must have admin option on role "pg_write_all_data"
+ERROR: permission denied to grant role "pg_write_all_data"
+DETAIL: You must have ADMIN OPTION on role "pg_write_all_data".
CREATE ROLE regress_monitor IN ROLE pg_monitor;
-ERROR: must have admin option on role "pg_monitor"
+ERROR: permission denied to grant role "pg_monitor"
+DETAIL: You must have ADMIN OPTION on role "pg_monitor".
CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings;
-ERROR: must have admin option on role "pg_read_all_settings"
+ERROR: permission denied to grant role "pg_read_all_settings"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_settings".
CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats;
-ERROR: must have admin option on role "pg_read_all_stats"
+ERROR: permission denied to grant role "pg_read_all_stats"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_stats".
CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables;
-ERROR: must have admin option on role "pg_stat_scan_tables"
+ERROR: permission denied to grant role "pg_stat_scan_tables"
+DETAIL: You must have ADMIN OPTION on role "pg_stat_scan_tables".
CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files;
-ERROR: must have admin option on role "pg_read_server_files"
+ERROR: permission denied to grant role "pg_read_server_files"
+DETAIL: You must have ADMIN OPTION on role "pg_read_server_files".
CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files;
-ERROR: must have admin option on role "pg_write_server_files"
+ERROR: permission denied to grant role "pg_write_server_files"
+DETAIL: You must have ADMIN OPTION on role "pg_write_server_files".
CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program;
-ERROR: must have admin option on role "pg_execute_server_program"
+ERROR: permission denied to grant role "pg_execute_server_program"
+DETAIL: You must have ADMIN OPTION on role "pg_execute_server_program".
CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend;
-ERROR: must have admin option on role "pg_signal_backend"
+ERROR: permission denied to grant role "pg_signal_backend"
+DETAIL: You must have ADMIN OPTION on role "pg_signal_backend".
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
@@ -211,11 +236,13 @@ DROP ROLE regress_inroles;
DROP ROLE regress_adminroles;
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
DROP ROLE regress_role_super;
-ERROR: must be superuser to drop superusers
+ERROR: permission denied to drop role
+DETAIL: You must have SUPERUSER privilege to drop roles with SUPERUSER.
DROP ROLE regress_role_admin;
ERROR: current user cannot be dropped
DROP ROLE regress_rolecreator;
-ERROR: must have admin option on role "regress_rolecreator"
+ERROR: permission denied to drop role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_rolecreator".
-- ok
RESET SESSION AUTHORIZATION;
REVOKE CREATE ON DATABASE regression FROM regress_role_admin CASCADE;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 520035f6a0..78ee4d7448 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -48,12 +48,16 @@ SET SESSION AUTHORIZATION regress_dep_user0;
-- permission denied
DROP OWNED BY regress_dep_user1;
ERROR: permission denied to drop objects
+DETAIL: You must have privileges of role "regress_dep_user1".
DROP OWNED BY regress_dep_user0, regress_dep_user2;
ERROR: permission denied to drop objects
+DETAIL: You must have privileges of role "regress_dep_user2".
REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_dep_user1".
REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_dep_user1".
-- this one is allowed
DROP OWNED BY regress_dep_user0;
CREATE TABLE deptest1 (f1 int unique);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..30c1d11a7c 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -37,7 +37,7 @@ CREATE ROLE regress_priv_role;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3;
-ERROR: admin option cannot be granted back to your own grantor
+ERROR: ADMIN OPTION cannot be granted back to your own grantor
-- need CASCADE to revoke grant or admin option if dependent grants exist
REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail
ERROR: dependent privileges exist
@@ -156,7 +156,8 @@ ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate
NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1"
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted
-ERROR: must have CREATEROLE privilege to change another user's password
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_priv_user2".
RESET SESSION AUTHORIZATION;
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1;
@@ -168,7 +169,8 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1;
-- test owner privileges
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION
-ERROR: grantor must have ADMIN OPTION on "regress_priv_role"
+ERROR: permission denied to grant privileges as role "regress_priv_role"
+DETAIL: The grantor must have ADMIN OPTION on role "regress_priv_role".
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE;
REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error
ERROR: role "foo" does not exist
@@ -1795,7 +1797,8 @@ REFRESH MATERIALIZED VIEW sro_mv;
ERROR: cannot fire deferred trigger within security-restricted operation
CONTEXT: SQL function "mv_action" statement 1
BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
CONTEXT: SQL function "unwanted_grant" statement 1
SQL statement "SELECT unwanted_grant()"
PL/pgSQL function sro_trojan() line 1 at PERFORM
@@ -1825,10 +1828,12 @@ CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4"
dogrant_ok
@@ -1838,10 +1843,12 @@ NOTICE: role "regress_priv_user5" has already been granted membership in role "
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user4;
DROP FUNCTION dogrant_ok();
REVOKE regress_priv_group2 FROM regress_priv_user5;
--
2.25.1
On Thu, Jan 26, 2023 at 2:14 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
Yeah, it's probably better to say "to alter roles with %s" to refer to
roles that presently have the attribute and "to change the %s attribute"
when referring to privileges for the attribute. I did this in v2, too.I've also switched from errhint() to errdetail() as suggested by Tom.
This seems fine to me in general but I'm not entirely sure about this part:
@@ -758,16 +776,13 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
/* things an unprivileged user certainly can't do */
if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
- dvalidUntil || disreplication || dbypassRLS)
+ dvalidUntil || disreplication || dbypassRLS ||
+ (dpassword && roleid != currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
-
- /* an unprivileged user can change their own password */
- if (dpassword && roleid != currentUserId)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", rolename)));
}
else if (!superuser())
{
Basically my question is whether having one error message for all of
those cases is good enough, or whether we should be trying harder. I
don't mind if the conclusion is that it's OK as-is, and I'm not
entirely sure what would be better. But when I was working on this
code, all of those cases OR'd together feeding into a single error
message seemed a little sketchy to me, so I am wondering what others
think.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jan 26, 2023 at 02:42:05PM -0500, Robert Haas wrote:
@@ -758,16 +776,13 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt) { /* things an unprivileged user certainly can't do */ if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit || - dvalidUntil || disreplication || dbypassRLS) + dvalidUntil || disreplication || dbypassRLS || + (dpassword && roleid != currentUserId)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied"))); - - /* an unprivileged user can change their own password */ - if (dpassword && roleid != currentUserId) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must have CREATEROLE privilege to change another user's password"))); + errmsg("permission denied to alter role"), + errdetail("You must have %s privilege and %s on role \"%s\".", + "CREATEROLE", "ADMIN OPTION", rolename))); } else if (!superuser()) {Basically my question is whether having one error message for all of
those cases is good enough, or whether we should be trying harder. I
don't mind if the conclusion is that it's OK as-is, and I'm not
entirely sure what would be better. But when I was working on this
code, all of those cases OR'd together feeding into a single error
message seemed a little sketchy to me, so I am wondering what others
think.
I wondered the same thing, but I hesitated because I didn't want to change
too much in a patch for error messaging. I can give it a try.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Nathan Bossart <nathandbossart@gmail.com> writes:
On Thu, Jan 26, 2023 at 02:42:05PM -0500, Robert Haas wrote:
Basically my question is whether having one error message for all of
those cases is good enough, or whether we should be trying harder.
I think the password case needs to be kept separate, because the
conditions for it are different (specifically the exception that
you can alter your own password). Lumping the rest together
seems OK to me.
regards, tom lane
On Thu, Jan 26, 2023 at 03:07:43PM -0500, Tom Lane wrote:
Nathan Bossart <nathandbossart@gmail.com> writes:
On Thu, Jan 26, 2023 at 02:42:05PM -0500, Robert Haas wrote:
Basically my question is whether having one error message for all of
those cases is good enough, or whether we should be trying harder.I think the password case needs to be kept separate, because the
conditions for it are different (specifically the exception that
you can alter your own password). Lumping the rest together
seems OK to me.
Hm. In v2, the error message for both cases is the same:
ERROR: permission denied to alter role
DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_priv_user2".
We could add "to change its attributes" and "to change its password" to
separate the two, but I'm not sure that adds much. ISTM the current error
message for ALTER ROLE PASSWORD implies that you can change your own
password, and that's lost with my patch. Perhaps we should add an
errhint() with that information instead. WDYT?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Nathan Bossart <nathandbossart@gmail.com> writes:
On Thu, Jan 26, 2023 at 03:07:43PM -0500, Tom Lane wrote:
I think the password case needs to be kept separate, because the
conditions for it are different (specifically the exception that
you can alter your own password). Lumping the rest together
seems OK to me.
Hm. In v2, the error message for both cases is the same:
ERROR: permission denied to alter role
DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_priv_user2".
We could add "to change its attributes" and "to change its password" to
separate the two, but I'm not sure that adds much. ISTM the current error
message for ALTER ROLE PASSWORD implies that you can change your own
password, and that's lost with my patch. Perhaps we should add an
errhint() with that information instead. WDYT?
Well, it's not a hint. I think the above is fine for non-password
cases, but for passwords maybe
ERROR: permission denied to alter role password
DETAIL: To change another role's password, you must have CREATEROLE privilege and ADMIN OPTION on role "%s".
regards, tom lane
On Thu, Jan 26, 2023 at 05:41:32PM -0500, Tom Lane wrote:
Well, it's not a hint. I think the above is fine for non-password
cases, but for passwords maybeERROR: permission denied to alter role password
DETAIL: To change another role's password, you must have CREATEROLE privilege and ADMIN OPTION on role "%s".
Okay. I used this phrasing in v3.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v3-0001-Improve-user.c-error-messages.patchtext/x-diff; charset=us-asciiDownload
From 02938746f72d0545d6c5cc3d1e9c6dc30e6e4175 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 26 Jan 2023 11:05:13 -0800
Subject: [PATCH v3 1/1] Improve user.c error messages.
---
src/backend/commands/user.c | 166 ++++++++++++++++------
src/test/regress/expected/create_role.out | 77 ++++++----
src/test/regress/expected/dependency.out | 4 +
src/test/regress/expected/privileges.out | 23 +--
4 files changed, 196 insertions(+), 74 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 3a92e930c0..ad22a89298 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -316,23 +316,33 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles.",
+ "CREATEROLE")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create superusers")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb permission to create createdb users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "CREATEDB", "CREATEDB")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication permission to create replication users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "REPLICATION", "REPLICATION")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls to create bypassrls users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/*
@@ -744,10 +754,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
roleid = authform->oid;
/* To mess with a superuser in any way you gotta be superuser. */
- if (!superuser() && (authform->rolsuper || dissuper))
+ if (!superuser() && authform->rolsuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ if (!superuser() && dissuper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "SUPERUSER", "SUPERUSER")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -761,13 +779,17 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
dvalidUntil || disreplication || dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", rolename)));
/* an unprivileged user can change their own password */
if (dpassword && roleid != currentUserId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errdetail("To change another role's password, you must have %s privilege and %s on the role.",
+ "CREATEROLE", "ADMIN OPTION")));
}
else if (!superuser())
{
@@ -779,23 +801,30 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb privilege to change createdb attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "CREATEDB", "CREATEDB")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication privilege to change replication attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "REPLICATION", "REPLICATION")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\" to add members",
- rolename)));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s on role \"%s\" to add members.",
+ "ADMIN OPTION", rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
@@ -838,7 +867,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: bootstrap user must be superuser")));
+ errmsg("permission denied to alter role"),
+ errdetail("The bootstrap user must be superuser.")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
@@ -999,7 +1029,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1008,7 +1040,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATROLE", "ADMIN OPTION", NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
@@ -1038,7 +1072,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter settings globally")));
+ errmsg("permission denied to alter setting"),
+ errdetail("You must have %s privilege to alter settings globally.",
+ "SUPERUSER")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1061,7 +1097,9 @@ DropRole(DropRoleStmt *stmt)
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop role")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege and %s on the target roles.",
+ "CREATEROLE", "ADMIN OPTION")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
@@ -1131,12 +1169,15 @@ DropRole(DropRoleStmt *stmt)
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to drop superusers")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege to drop roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
@@ -1378,12 +1419,14 @@ RenameRole(const char *oldname, const char *newname)
* Only superusers can mess with superusers. Otherwise, a user with
* CREATEROLE can rename a role for which they have ADMIN OPTION.
*/
- if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+ if (authform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to rename superusers")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have %s privilege to rename roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1391,7 +1434,9 @@ RenameRole(const char *oldname, const char *newname)
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to rename role")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", NameStr(authform->rolname))));
}
/* OK, construct the modified tuple */
@@ -1554,7 +1599,9 @@ DropOwnedObjects(DropOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop objects")));
+ errmsg("permission denied to drop objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Ok, do it */
@@ -1581,7 +1628,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
@@ -1590,7 +1639,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
@@ -1738,7 +1789,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s cannot be granted back to your own grantor",
+ "ADMIN OPTION")));
plan_member_revoke(memlist, actions, memberid);
}
@@ -1763,7 +1815,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s cannot be granted back to your own grantor",
+ "ADMIN OPTION")));
ReleaseSysCacheList(memlist);
}
@@ -2081,9 +2134,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s privilege to grant roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s privilege to revoke roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ }
}
else
{
@@ -2091,10 +2157,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(roleid, false))));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
+ }
}
}
@@ -2173,14 +2251,18 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("grantor must have ADMIN OPTION on \"%s\"",
- GetUserNameFromId(roleid, false))));
+ errmsg("permission denied to grant privileges as role \"%s\"",
+ GetUserNameFromId(grantorId, false)),
+ errdetail("The grantor must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
}
else
{
@@ -2188,7 +2270,9 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
}
/*
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 9f431bd4f5..2a7d14dba9 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -7,26 +7,35 @@ CREATE ROLE regress_role_normal;
-- fail, CREATEROLE user can't give away role attributes without having them
SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
-ERROR: must be superuser to create superusers
+ERROR: permission denied to create role
+DETAIL: You must have SUPERUSER privilege to create roles with SUPERUSER.
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must have bypassrls to create bypassrls users
+ERROR: permission denied to create role
+DETAIL: You must have BYPASSRLS privilege to create roles with BYPASSRLS.
CREATE ROLE regress_nosuch_createdb CREATEDB;
-ERROR: must have createdb permission to create createdb users
+ERROR: permission denied to create role
+DETAIL: You must have CREATEDB privilege to create roles with CREATEDB.
-- ok, can create a role without any special attributes
CREATE ROLE regress_role_limited;
-- fail, can't give it in any of the restricted attributes
ALTER ROLE regress_role_limited SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
ALTER ROLE regress_role_limited REPLICATION;
-ERROR: must have replication privilege to change replication attribute
+ERROR: permission denied to alter role
+DETAIL: You must have REPLICATION privilege to change the REPLICATION attribute.
ALTER ROLE regress_role_limited CREATEDB;
-ERROR: must have createdb privilege to change createdb attribute
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEDB privilege to change the CREATEDB attribute.
ALTER ROLE regress_role_limited BYPASSRLS;
-ERROR: must have bypassrls privilege to change bypassrls attribute
+ERROR: permission denied to alter role
+DETAIL: You must have BYPASSRLS privilege to change the BYPASSRLS attribute.
DROP ROLE regress_role_limited;
-- ok, can give away these role attributes if you have them
SET SESSION AUTHORIZATION regress_role_admin;
@@ -43,9 +52,11 @@ ALTER ROLE regress_createdb NOCREATEDB;
ALTER ROLE regress_createdb CREATEDB;
-- fail, can't toggle SUPERUSER
ALTER ROLE regress_createdb SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
ALTER ROLE regress_createdb NOSUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
@@ -59,7 +70,8 @@ CREATE ROLE regress_noiseword SYSID 12345;
NOTICE: SYSID can no longer be specified
-- fail, cannot grant membership in superuser role
CREATE ROLE regress_nosuch_super IN ROLE regress_role_super;
-ERROR: must be superuser to alter superusers
+ERROR: permission denied to grant role "regress_role_super"
+DETAIL: You must have SUPERUSER privilege to grant roles with SUPERUSER.
-- fail, database owner cannot have members
CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner;
ERROR: role "pg_database_owner" cannot have explicit members
@@ -97,8 +109,10 @@ COMMENT ON ROLE regress_role_normal IS 'some comment';
ERROR: must have admin option on role "regress_role_normal"
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
ALTER ROLE regress_role_normal NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-ERROR: permission denied
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
@@ -123,6 +137,7 @@ ERROR: must be able to SET ROLE "regress_tenant"
-- fail, we don't inherit permissions from regress_tenant
REASSIGN OWNED BY regress_tenant TO regress_createrole;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_tenant".
-- ok, create a role with a value for createrole_self_grant
SET createrole_self_grant = 'set, inherit';
CREATE ROLE regress_tenant2;
@@ -150,25 +165,35 @@ ERROR: must be able to SET ROLE "regress_tenant2"
DROP TABLE tenant2_table;
-- fail, CREATEROLE is not enough to create roles in privileged roles
CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data;
-ERROR: must have admin option on role "pg_read_all_data"
+ERROR: permission denied to grant role "pg_read_all_data"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_data".
CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data;
-ERROR: must have admin option on role "pg_write_all_data"
+ERROR: permission denied to grant role "pg_write_all_data"
+DETAIL: You must have ADMIN OPTION on role "pg_write_all_data".
CREATE ROLE regress_monitor IN ROLE pg_monitor;
-ERROR: must have admin option on role "pg_monitor"
+ERROR: permission denied to grant role "pg_monitor"
+DETAIL: You must have ADMIN OPTION on role "pg_monitor".
CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings;
-ERROR: must have admin option on role "pg_read_all_settings"
+ERROR: permission denied to grant role "pg_read_all_settings"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_settings".
CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats;
-ERROR: must have admin option on role "pg_read_all_stats"
+ERROR: permission denied to grant role "pg_read_all_stats"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_stats".
CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables;
-ERROR: must have admin option on role "pg_stat_scan_tables"
+ERROR: permission denied to grant role "pg_stat_scan_tables"
+DETAIL: You must have ADMIN OPTION on role "pg_stat_scan_tables".
CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files;
-ERROR: must have admin option on role "pg_read_server_files"
+ERROR: permission denied to grant role "pg_read_server_files"
+DETAIL: You must have ADMIN OPTION on role "pg_read_server_files".
CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files;
-ERROR: must have admin option on role "pg_write_server_files"
+ERROR: permission denied to grant role "pg_write_server_files"
+DETAIL: You must have ADMIN OPTION on role "pg_write_server_files".
CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program;
-ERROR: must have admin option on role "pg_execute_server_program"
+ERROR: permission denied to grant role "pg_execute_server_program"
+DETAIL: You must have ADMIN OPTION on role "pg_execute_server_program".
CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend;
-ERROR: must have admin option on role "pg_signal_backend"
+ERROR: permission denied to grant role "pg_signal_backend"
+DETAIL: You must have ADMIN OPTION on role "pg_signal_backend".
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
@@ -211,11 +236,13 @@ DROP ROLE regress_inroles;
DROP ROLE regress_adminroles;
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
DROP ROLE regress_role_super;
-ERROR: must be superuser to drop superusers
+ERROR: permission denied to drop role
+DETAIL: You must have SUPERUSER privilege to drop roles with SUPERUSER.
DROP ROLE regress_role_admin;
ERROR: current user cannot be dropped
DROP ROLE regress_rolecreator;
-ERROR: must have admin option on role "regress_rolecreator"
+ERROR: permission denied to drop role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_rolecreator".
-- ok
RESET SESSION AUTHORIZATION;
REVOKE CREATE ON DATABASE regression FROM regress_role_admin CASCADE;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 520035f6a0..78ee4d7448 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -48,12 +48,16 @@ SET SESSION AUTHORIZATION regress_dep_user0;
-- permission denied
DROP OWNED BY regress_dep_user1;
ERROR: permission denied to drop objects
+DETAIL: You must have privileges of role "regress_dep_user1".
DROP OWNED BY regress_dep_user0, regress_dep_user2;
ERROR: permission denied to drop objects
+DETAIL: You must have privileges of role "regress_dep_user2".
REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_dep_user1".
REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_dep_user1".
-- this one is allowed
DROP OWNED BY regress_dep_user0;
CREATE TABLE deptest1 (f1 int unique);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..6794645e56 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -37,7 +37,7 @@ CREATE ROLE regress_priv_role;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3;
-ERROR: admin option cannot be granted back to your own grantor
+ERROR: ADMIN OPTION cannot be granted back to your own grantor
-- need CASCADE to revoke grant or admin option if dependent grants exist
REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail
ERROR: dependent privileges exist
@@ -156,7 +156,8 @@ ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate
NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1"
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted
-ERROR: must have CREATEROLE privilege to change another user's password
+ERROR: permission denied to alter role
+DETAIL: To change another role's password, you must have CREATEROLE privilege and ADMIN OPTION on the role.
RESET SESSION AUTHORIZATION;
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1;
@@ -168,7 +169,8 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1;
-- test owner privileges
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION
-ERROR: grantor must have ADMIN OPTION on "regress_priv_role"
+ERROR: permission denied to grant privileges as role "regress_priv_role"
+DETAIL: The grantor must have ADMIN OPTION on role "regress_priv_role".
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE;
REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error
ERROR: role "foo" does not exist
@@ -1795,7 +1797,8 @@ REFRESH MATERIALIZED VIEW sro_mv;
ERROR: cannot fire deferred trigger within security-restricted operation
CONTEXT: SQL function "mv_action" statement 1
BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
CONTEXT: SQL function "unwanted_grant" statement 1
SQL statement "SELECT unwanted_grant()"
PL/pgSQL function sro_trojan() line 1 at PERFORM
@@ -1825,10 +1828,12 @@ CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4"
dogrant_ok
@@ -1838,10 +1843,12 @@ NOTICE: role "regress_priv_user5" has already been granted membership in role "
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user4;
DROP FUNCTION dogrant_ok();
REVOKE regress_priv_group2 FROM regress_priv_user5;
--
2.25.1
On 26.01.23 01:22, Nathan Bossart wrote:
Here is an early draft of some modest improvements to the user.c error
messages. I basically just tried to standardize the style of and add
context to the existing error messages. I used errhint() for this extra
context, but errdetail() would work, too. This isn't perfect. You might
still have to go through a couple rounds of errors before your role has all
the privileges it needs for a command, but this seems to improve matters a
little.I think there is still a lot of room for improvement, but I wanted to at
least get the discussion started before I went too far.
This is good. If I may assign some more work ;-), we have a bunch of
error messages like
errmsg("must be superuser or a role with privileges of the
pg_write_server_files role to create backup stored on server")
errmsg("must be superuser or have privileges of the
pg_execute_server_program role to COPY to or from an external program")
errmsg("must be superuser or have privileges of pg_read_all_settings to
examine \"%s\"", ...)
which could also be split up into a pair of
errmsg("permission denied to xxx")
errdetail("You must be superuser or ...")
On Fri, Jan 27, 2023 at 5:00 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
This is good. If I may assign some more work ;-), we have a bunch of
error messages likeerrmsg("must be superuser or a role with privileges of the
pg_write_server_files role to create backup stored on server")errmsg("must be superuser or have privileges of the
pg_execute_server_program role to COPY to or from an external program")errmsg("must be superuser or have privileges of pg_read_all_settings to
examine \"%s\"", ...)which could also be split up into a pair of
errmsg("permission denied to xxx")
errdetail("You must be superuser or ...")
I almost hate to bring this up since I'm not sure how far we want to
go down this rat hole, but what should be our policy about mentioning
superuser? I don't think we're entirely consistent right now, and I'm
not sure whether every error message needs to mention that if you were
the superuser you could do everything. Is that something we should
mention always, never, or in some set of circumstances?
--
Robert Haas
EDB: http://www.enterprisedb.com
On Fri, Jan 27, 2023 at 08:31:32AM -0500, Robert Haas wrote:
I almost hate to bring this up since I'm not sure how far we want to
go down this rat hole, but what should be our policy about mentioning
superuser? I don't think we're entirely consistent right now, and I'm
not sure whether every error message needs to mention that if you were
the superuser you could do everything. Is that something we should
mention always, never, or in some set of circumstances?
IMHO superuser should typically only be mentioned when it is the only way
to do something. Since superusers have all privileges, I think logs like
"superuser or privileges of X" are kind of redundant. If Robert has
privileges of X, we wouldn't say "privileges of X or Robert." We'd just
point to X. Ultimately, I feel like mentioning superuser in error messages
usually just makes the message longer without adding any useful
information.
I recognize that this is a bold opinion and that the policy to mention
superuser might need to be more nuanced in practice...
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jan 27, 2023 at 10:52 AM Nathan Bossart
<nathandbossart@gmail.com> wrote:
IMHO superuser should typically only be mentioned when it is the only way
to do something. Since superusers have all privileges, I think logs like
"superuser or privileges of X" are kind of redundant. If Robert has
privileges of X, we wouldn't say "privileges of X or Robert." We'd just
point to X. Ultimately, I feel like mentioning superuser in error messages
usually just makes the message longer without adding any useful
information.
That's kind of my opinion too, but I'm not sure whether there are
cases where it will lead to confusion.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
I almost hate to bring this up since I'm not sure how far we want to
go down this rat hole, but what should be our policy about mentioning
superuser? I don't think we're entirely consistent right now, and I'm
not sure whether every error message needs to mention that if you were
the superuser you could do everything. Is that something we should
mention always, never, or in some set of circumstances?
Good point. My vote is for standardizing on *not* mentioning it.
Error messages should say "you need privilege X". That is not
the place to go into all the ways you could hold privilege X
(one of which is being superuser).
regards, tom lane
On 2023-Jan-27, Tom Lane wrote:
Good point. My vote is for standardizing on *not* mentioning it.
Error messages should say "you need privilege X". That is not
the place to go into all the ways you could hold privilege X
(one of which is being superuser).
+1
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"El sabio habla porque tiene algo que decir;
el tonto, porque tiene que decir algo" (Platon).
While we're here,
On 2023-Jan-26, Nathan Bossart wrote:
@@ -838,7 +867,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt) if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: bootstrap user must be superuser"))); + errmsg("permission denied to alter role"), + errdetail("The bootstrap user must be superuser.")));
I think this one isn't using the right errcode; this is not a case of
insufficient privileges. There's no priv you can acquire that lets you
do it. So I'd change it to unsupported operation.
I was confused a bit by this one:
/* an unprivileged user can change their own password */ if (dpassword && roleid != currentUserId) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must have CREATEROLE privilege to change another user's password"))); + errmsg("permission denied to alter role"), + errdetail("To change another role's password, you must have %s privilege and %s on the role.", + "CREATEROLE", "ADMIN OPTION"))); }
In no other message we say what operation is being attempted in the
errdetail; all the others start with "You must have" and that's it.
However, looking closer I think this one being different is okay,
because the errmsg() you're using is vague, and I think the error report
would be confusing if you were to remove the "To change another role's
password" bit.
The patch looks good to me.
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
On Fri, Jan 27, 2023 at 07:31:19PM +0100, Alvaro Herrera wrote:
On 2023-Jan-26, Nathan Bossart wrote:
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: bootstrap user must be superuser"))); + errmsg("permission denied to alter role"), + errdetail("The bootstrap user must be superuser.")));I think this one isn't using the right errcode; this is not a case of
insufficient privileges. There's no priv you can acquire that lets you
do it. So I'd change it to unsupported operation.
І fixed this in v4. I've also attached a second patch in which I've
adjusted the messages that Peter mentioned upthread.
One thing that feels a bit odd is how some of the DETAILs mention the
operation being attempted while others do not. For example, we have
ERROR: permission denied to drop role
DETAIL: You must have SUPERUSER privilege to drop roles with SUPERUSER.
In this case, the DETAIL explains the action that is prohibited. In other
cases, we have something like
ERROR: permission denied to alter role
DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "myrole".
which does not. I think this is okay because adding "to alter the role" to
the end of the DETAIL seems kind of awkward. But in other cases, such as
ERROR: permission denied to use replication slots
DETAIL: You must have REPLICATION privilege.
adding the operation to the end seems less awkward (i.e., "You must have
REPLICATION privilege to use replication slots."). I don't think there's
any information lost by omitting the action in the DETAIL, so perhaps this
is just a stylistic choice. I think I'm inclined to add the action to the
DETAIL whenever it doesn't make the message lengthy and awkward, and leave
it out otherwise. Thoughts?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v4-0001-Improve-user.c-error-messages.patchtext/x-diff; charset=us-asciiDownload
From d369e35e6a44cb9708a08a1f32d1755e04f04de1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 26 Jan 2023 11:05:13 -0800
Subject: [PATCH v4 1/2] Improve user.c error messages.
---
src/backend/commands/user.c | 168 ++++++++++++++++------
src/test/regress/expected/create_role.out | 77 ++++++----
src/test/regress/expected/dependency.out | 4 +
src/test/regress/expected/privileges.out | 23 +--
4 files changed, 197 insertions(+), 75 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 3a92e930c0..26b533f1be 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -316,23 +316,33 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles.",
+ "CREATEROLE")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create superusers")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb permission to create createdb users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "CREATEDB", "CREATEDB")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication permission to create replication users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "REPLICATION", "REPLICATION")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls to create bypassrls users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/*
@@ -744,10 +754,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
roleid = authform->oid;
/* To mess with a superuser in any way you gotta be superuser. */
- if (!superuser() && (authform->rolsuper || dissuper))
+ if (!superuser() && authform->rolsuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ if (!superuser() && dissuper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "SUPERUSER", "SUPERUSER")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -761,13 +779,17 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
dvalidUntil || disreplication || dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", rolename)));
/* an unprivileged user can change their own password */
if (dpassword && roleid != currentUserId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errdetail("To change another role's password, you must have %s privilege and %s on the role.",
+ "CREATEROLE", "ADMIN OPTION")));
}
else if (!superuser())
{
@@ -779,23 +801,30 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb privilege to change createdb attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "CREATEDB", "CREATEDB")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication privilege to change replication attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "REPLICATION", "REPLICATION")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\" to add members",
- rolename)));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s on role \"%s\" to add members.",
+ "ADMIN OPTION", rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
@@ -837,8 +866,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: bootstrap user must be superuser")));
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("permission denied to alter role"),
+ errdetail("The bootstrap user must be superuser.")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
@@ -999,7 +1029,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1008,7 +1040,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATROLE", "ADMIN OPTION", NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
@@ -1038,7 +1072,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter settings globally")));
+ errmsg("permission denied to alter setting"),
+ errdetail("You must have %s privilege to alter settings globally.",
+ "SUPERUSER")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1061,7 +1097,9 @@ DropRole(DropRoleStmt *stmt)
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop role")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege and %s on the target roles.",
+ "CREATEROLE", "ADMIN OPTION")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
@@ -1131,12 +1169,15 @@ DropRole(DropRoleStmt *stmt)
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to drop superusers")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege to drop roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
@@ -1378,12 +1419,14 @@ RenameRole(const char *oldname, const char *newname)
* Only superusers can mess with superusers. Otherwise, a user with
* CREATEROLE can rename a role for which they have ADMIN OPTION.
*/
- if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+ if (authform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to rename superusers")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have %s privilege to rename roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1391,7 +1434,9 @@ RenameRole(const char *oldname, const char *newname)
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to rename role")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", NameStr(authform->rolname))));
}
/* OK, construct the modified tuple */
@@ -1554,7 +1599,9 @@ DropOwnedObjects(DropOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop objects")));
+ errmsg("permission denied to drop objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Ok, do it */
@@ -1581,7 +1628,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
@@ -1590,7 +1639,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
@@ -1738,7 +1789,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s cannot be granted back to your own grantor",
+ "ADMIN OPTION")));
plan_member_revoke(memlist, actions, memberid);
}
@@ -1763,7 +1815,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s cannot be granted back to your own grantor",
+ "ADMIN OPTION")));
ReleaseSysCacheList(memlist);
}
@@ -2081,9 +2134,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s privilege to grant roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s privilege to revoke roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ }
}
else
{
@@ -2091,10 +2157,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(roleid, false))));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
+ }
}
}
@@ -2173,14 +2251,18 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("grantor must have ADMIN OPTION on \"%s\"",
- GetUserNameFromId(roleid, false))));
+ errmsg("permission denied to grant privileges as role \"%s\"",
+ GetUserNameFromId(grantorId, false)),
+ errdetail("The grantor must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
}
else
{
@@ -2188,7 +2270,9 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
}
/*
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 9f431bd4f5..2a7d14dba9 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -7,26 +7,35 @@ CREATE ROLE regress_role_normal;
-- fail, CREATEROLE user can't give away role attributes without having them
SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
-ERROR: must be superuser to create superusers
+ERROR: permission denied to create role
+DETAIL: You must have SUPERUSER privilege to create roles with SUPERUSER.
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must have bypassrls to create bypassrls users
+ERROR: permission denied to create role
+DETAIL: You must have BYPASSRLS privilege to create roles with BYPASSRLS.
CREATE ROLE regress_nosuch_createdb CREATEDB;
-ERROR: must have createdb permission to create createdb users
+ERROR: permission denied to create role
+DETAIL: You must have CREATEDB privilege to create roles with CREATEDB.
-- ok, can create a role without any special attributes
CREATE ROLE regress_role_limited;
-- fail, can't give it in any of the restricted attributes
ALTER ROLE regress_role_limited SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
ALTER ROLE regress_role_limited REPLICATION;
-ERROR: must have replication privilege to change replication attribute
+ERROR: permission denied to alter role
+DETAIL: You must have REPLICATION privilege to change the REPLICATION attribute.
ALTER ROLE regress_role_limited CREATEDB;
-ERROR: must have createdb privilege to change createdb attribute
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEDB privilege to change the CREATEDB attribute.
ALTER ROLE regress_role_limited BYPASSRLS;
-ERROR: must have bypassrls privilege to change bypassrls attribute
+ERROR: permission denied to alter role
+DETAIL: You must have BYPASSRLS privilege to change the BYPASSRLS attribute.
DROP ROLE regress_role_limited;
-- ok, can give away these role attributes if you have them
SET SESSION AUTHORIZATION regress_role_admin;
@@ -43,9 +52,11 @@ ALTER ROLE regress_createdb NOCREATEDB;
ALTER ROLE regress_createdb CREATEDB;
-- fail, can't toggle SUPERUSER
ALTER ROLE regress_createdb SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
ALTER ROLE regress_createdb NOSUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
@@ -59,7 +70,8 @@ CREATE ROLE regress_noiseword SYSID 12345;
NOTICE: SYSID can no longer be specified
-- fail, cannot grant membership in superuser role
CREATE ROLE regress_nosuch_super IN ROLE regress_role_super;
-ERROR: must be superuser to alter superusers
+ERROR: permission denied to grant role "regress_role_super"
+DETAIL: You must have SUPERUSER privilege to grant roles with SUPERUSER.
-- fail, database owner cannot have members
CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner;
ERROR: role "pg_database_owner" cannot have explicit members
@@ -97,8 +109,10 @@ COMMENT ON ROLE regress_role_normal IS 'some comment';
ERROR: must have admin option on role "regress_role_normal"
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
ALTER ROLE regress_role_normal NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-ERROR: permission denied
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
@@ -123,6 +137,7 @@ ERROR: must be able to SET ROLE "regress_tenant"
-- fail, we don't inherit permissions from regress_tenant
REASSIGN OWNED BY regress_tenant TO regress_createrole;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_tenant".
-- ok, create a role with a value for createrole_self_grant
SET createrole_self_grant = 'set, inherit';
CREATE ROLE regress_tenant2;
@@ -150,25 +165,35 @@ ERROR: must be able to SET ROLE "regress_tenant2"
DROP TABLE tenant2_table;
-- fail, CREATEROLE is not enough to create roles in privileged roles
CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data;
-ERROR: must have admin option on role "pg_read_all_data"
+ERROR: permission denied to grant role "pg_read_all_data"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_data".
CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data;
-ERROR: must have admin option on role "pg_write_all_data"
+ERROR: permission denied to grant role "pg_write_all_data"
+DETAIL: You must have ADMIN OPTION on role "pg_write_all_data".
CREATE ROLE regress_monitor IN ROLE pg_monitor;
-ERROR: must have admin option on role "pg_monitor"
+ERROR: permission denied to grant role "pg_monitor"
+DETAIL: You must have ADMIN OPTION on role "pg_monitor".
CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings;
-ERROR: must have admin option on role "pg_read_all_settings"
+ERROR: permission denied to grant role "pg_read_all_settings"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_settings".
CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats;
-ERROR: must have admin option on role "pg_read_all_stats"
+ERROR: permission denied to grant role "pg_read_all_stats"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_stats".
CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables;
-ERROR: must have admin option on role "pg_stat_scan_tables"
+ERROR: permission denied to grant role "pg_stat_scan_tables"
+DETAIL: You must have ADMIN OPTION on role "pg_stat_scan_tables".
CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files;
-ERROR: must have admin option on role "pg_read_server_files"
+ERROR: permission denied to grant role "pg_read_server_files"
+DETAIL: You must have ADMIN OPTION on role "pg_read_server_files".
CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files;
-ERROR: must have admin option on role "pg_write_server_files"
+ERROR: permission denied to grant role "pg_write_server_files"
+DETAIL: You must have ADMIN OPTION on role "pg_write_server_files".
CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program;
-ERROR: must have admin option on role "pg_execute_server_program"
+ERROR: permission denied to grant role "pg_execute_server_program"
+DETAIL: You must have ADMIN OPTION on role "pg_execute_server_program".
CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend;
-ERROR: must have admin option on role "pg_signal_backend"
+ERROR: permission denied to grant role "pg_signal_backend"
+DETAIL: You must have ADMIN OPTION on role "pg_signal_backend".
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
@@ -211,11 +236,13 @@ DROP ROLE regress_inroles;
DROP ROLE regress_adminroles;
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
DROP ROLE regress_role_super;
-ERROR: must be superuser to drop superusers
+ERROR: permission denied to drop role
+DETAIL: You must have SUPERUSER privilege to drop roles with SUPERUSER.
DROP ROLE regress_role_admin;
ERROR: current user cannot be dropped
DROP ROLE regress_rolecreator;
-ERROR: must have admin option on role "regress_rolecreator"
+ERROR: permission denied to drop role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_rolecreator".
-- ok
RESET SESSION AUTHORIZATION;
REVOKE CREATE ON DATABASE regression FROM regress_role_admin CASCADE;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 520035f6a0..78ee4d7448 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -48,12 +48,16 @@ SET SESSION AUTHORIZATION regress_dep_user0;
-- permission denied
DROP OWNED BY regress_dep_user1;
ERROR: permission denied to drop objects
+DETAIL: You must have privileges of role "regress_dep_user1".
DROP OWNED BY regress_dep_user0, regress_dep_user2;
ERROR: permission denied to drop objects
+DETAIL: You must have privileges of role "regress_dep_user2".
REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_dep_user1".
REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_dep_user1".
-- this one is allowed
DROP OWNED BY regress_dep_user0;
CREATE TABLE deptest1 (f1 int unique);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..6794645e56 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -37,7 +37,7 @@ CREATE ROLE regress_priv_role;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3;
-ERROR: admin option cannot be granted back to your own grantor
+ERROR: ADMIN OPTION cannot be granted back to your own grantor
-- need CASCADE to revoke grant or admin option if dependent grants exist
REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail
ERROR: dependent privileges exist
@@ -156,7 +156,8 @@ ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate
NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1"
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted
-ERROR: must have CREATEROLE privilege to change another user's password
+ERROR: permission denied to alter role
+DETAIL: To change another role's password, you must have CREATEROLE privilege and ADMIN OPTION on the role.
RESET SESSION AUTHORIZATION;
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1;
@@ -168,7 +169,8 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1;
-- test owner privileges
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION
-ERROR: grantor must have ADMIN OPTION on "regress_priv_role"
+ERROR: permission denied to grant privileges as role "regress_priv_role"
+DETAIL: The grantor must have ADMIN OPTION on role "regress_priv_role".
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE;
REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error
ERROR: role "foo" does not exist
@@ -1795,7 +1797,8 @@ REFRESH MATERIALIZED VIEW sro_mv;
ERROR: cannot fire deferred trigger within security-restricted operation
CONTEXT: SQL function "mv_action" statement 1
BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
CONTEXT: SQL function "unwanted_grant" statement 1
SQL statement "SELECT unwanted_grant()"
PL/pgSQL function sro_trojan() line 1 at PERFORM
@@ -1825,10 +1828,12 @@ CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4"
dogrant_ok
@@ -1838,10 +1843,12 @@ NOTICE: role "regress_priv_user5" has already been granted membership in role "
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user4;
DROP FUNCTION dogrant_ok();
REVOKE regress_priv_group2 FROM regress_priv_user5;
--
2.25.1
v4-0002-Improve-more-insufficient-privileges-error-messag.patchtext/x-diff; charset=us-asciiDownload
From 61248faddb4ec9d1bcf4e4eca3c1315b718150f0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 14:52:57 -0800
Subject: [PATCH v4 2/2] Improve more insufficient-privileges error messages.
---
contrib/file_fdw/expected/file_fdw.out | 3 ++-
contrib/file_fdw/file_fdw.c | 8 ++++++--
contrib/test_decoding/expected/permissions.out | 12 ++++++++----
src/backend/backup/basebackup_server.c | 4 +++-
src/backend/catalog/objectaddress.c | 16 +++++++++++-----
src/backend/commands/copy.c | 12 +++++++++---
src/backend/replication/slot.c | 5 +++--
src/backend/storage/ipc/procarray.c | 4 +++-
src/backend/storage/ipc/signalfuncs.c | 16 ++++++++++++----
src/backend/tcop/utility.c | 4 +++-
src/backend/utils/init/miscinit.c | 4 ++++
src/backend/utils/init/postinit.c | 8 +++++---
src/backend/utils/misc/guc.c | 15 +++++++++------
.../dummy_seclabel/expected/dummy_seclabel.out | 3 ++-
.../modules/unsafe_tests/expected/rolenames.out | 3 ++-
src/test/regress/expected/create_role.out | 3 ++-
16 files changed, 84 insertions(+), 36 deletions(-)
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 36d76ba26c..7e57ad2990 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -474,7 +474,8 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: permission denied to specify the "filename" option of a file_fdw foreign table
+DETAIL: You must have privileges of the "pg_read_server_files" role.
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 8ccc167548..696d9e5718 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -278,13 +278,17 @@ file_fdw_validator(PG_FUNCTION_ARGS)
!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("permission denied to specify the \"filename\" option of a file_fdw foreign table"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_read_server_files")));
if (strcmp(def->defname, "program") == 0 &&
!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("permission denied to specify the \"program\" option of a file_fdw foreign table"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_execute_server_program")));
filename = defGetString(def);
}
diff --git a/contrib/test_decoding/expected/permissions.out b/contrib/test_decoding/expected/permissions.out
index ed97f81dda..d102ff066b 100644
--- a/contrib/test_decoding/expected/permissions.out
+++ b/contrib/test_decoding/expected/permissions.out
@@ -54,13 +54,16 @@ RESET ROLE;
-- plain user *can't* can control replication
SET ROLE regress_lr_normal;
SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have REPLICATION privilege.
INSERT INTO lr_test VALUES('lr_superuser_init');
ERROR: permission denied for table lr_test
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have REPLICATION privilege.
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have REPLICATION privilege.
RESET ROLE;
-- replication users can drop superuser created slots
SET ROLE regress_lr_superuser;
@@ -90,7 +93,8 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_d
RESET ROLE;
SET ROLE regress_lr_normal;
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have REPLICATION privilege.
RESET ROLE;
-- all users can see existing slots
SET ROLE regress_lr_superuser;
diff --git a/src/backend/backup/basebackup_server.c b/src/backend/backup/basebackup_server.c
index 0258d7a03b..de89df190f 100644
--- a/src/backend/backup/basebackup_server.c
+++ b/src/backend/backup/basebackup_server.c
@@ -72,7 +72,9 @@ bbsink_server_new(bbsink *next, char *pathname)
if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a role with privileges of the pg_write_server_files role to create backup stored on server")));
+ errmsg("permission denied to create backup stored on server"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_write_server_files")));
CommitTransactionCommand();
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 25c50d66fd..2a39ce0567 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2547,20 +2547,26 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
if (!superuser_arg(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser")));
+ errmsg("permission denied"),
+ errdetail("You must have %s privilege.",
+ "SUPERUSER")));
}
else
{
if (!has_createrole_privilege(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege")));
+ errmsg("permission denied"),
+ errdetail("You must have %s privilege.",
+ "CREATEROLE")));
if (!is_admin_of_role(roleid, address.objectId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(address.objectId,
- true))));
+ errmsg("permission denied"),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION",
+ GetUserNameFromId(address.objectId,
+ true))));
}
break;
case OBJECT_TSPARSER:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e34f583ea7..e557eb17b6 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -83,7 +83,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("permission denied to COPY to or from an external program"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_execute_server_program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
@@ -92,14 +94,18 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
+ errmsg("permission denied to COPY from a file"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_read_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
+ errmsg("permission denied to COPY to a file"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_write_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index f286918f69..e4df06a05d 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1140,10 +1140,11 @@ CheckSlotRequirements(void)
void
CheckSlotPermissions(void)
{
- if (!superuser() && !has_rolreplication(GetUserId()))
+ if (!has_rolreplication(GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to use replication slots")));
+ errmsg("permission denied to use replication slots"),
+ errdetail("You must have %s privilege.", "REPLICATION")));
}
/*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4340bf9641..95967f8dfa 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3860,7 +3860,9 @@ TerminateOtherDBBackends(Oid databaseId)
!has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must have privileges of the role whose process is being terminated or have privileges of the \"%s\" role.",
+ "pg_signal_backend")));
}
}
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index bc93ab5b52..7e0f0aef73 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -121,12 +121,16 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to cancel superuser query")));
+ errmsg("permission denied to cancel query"),
+ errdetail("You must have %s privilege to cancel queries of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend")));
+ errmsg("permission denied to cancel query"),
+ errdetail("You must have privileges of the role whose query is being canceled or have privileges of the \"%s\" role.",
+ "pg_signal_backend")));
PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
}
@@ -223,12 +227,16 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to terminate superuser process")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must have %s privilege to terminate processes of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must have privileges of the role whose process is being terminated or have privileges of the \"%s\" role.",
+ "pg_signal_backend")));
/* Wait only on success and if actually requested */
if (r == SIGNAL_BACKEND_SUCCESS && timeout > 0)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c7d9d96b45..fe77f260c5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -950,7 +950,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_checkpoint to do CHECKPOINT")));
+ errmsg("permission denied to do CHECKPOINT"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_checkpoint")));
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 0cdc1e11a3..3a98fafaeb 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -701,6 +701,10 @@ has_rolreplication(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))
{
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 2f07ca7a0e..808c24b480 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -945,7 +945,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_connections")));
+ errmsg("remaining connection slots are reserved for roles with privileges of \"%s\"",
+ "pg_use_reserved_connections")));
}
/* Check replication permissions needed for walsender processes. */
@@ -953,10 +954,11 @@ InitPostgres(const char *in_dbname, Oid dboid,
{
Assert(!bootstrap);
- if (!superuser() && !has_rolreplication(GetUserId()))
+ if (!has_rolreplication(GetUserId()))
ereport(FATAL,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to start walsender")));
+ errmsg("permission denied to start walsender"),
+ errdetail("You must have %s privilege.", "REPLICATION")));
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 978b385568..07d97ad75e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4190,8 +4190,9 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -4236,8 +4237,9 @@ GetConfigOptionResetString(const char *name)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -5242,8 +5244,9 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_read_all_settings")));
if (varname)
*varname = record->name;
diff --git a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
index c57d4fd2df..abb0b2fa0f 100644
--- a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
+++ b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
@@ -59,7 +59,8 @@ SECURITY LABEL ON ROLE regress_dummy_seclabel_user4 IS 'unclassified'; -- fail (
ERROR: role "regress_dummy_seclabel_user4" does not exist
SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
SECURITY LABEL ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- fail (not privileged)
-ERROR: must have CREATEROLE privilege
+ERROR: permission denied
+DETAIL: You must have CREATEROLE privilege.
RESET SESSION AUTHORIZATION;
--
-- Test for various types of object
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index 88b1ff843b..6f1fc210a7 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,8 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: permission denied to examine "session_preload_libraries"
+DETAIL: You must have privileges of the "pg_read_all_settings" role.
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 2a7d14dba9..d9620fb8d7 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -106,7 +106,8 @@ ALTER ROLE regress_hasprivs RENAME TO regress_tenant;
ALTER ROLE regress_tenant NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-- fail, we should be unable to modify a role we did not create
COMMENT ON ROLE regress_role_normal IS 'some comment';
-ERROR: must have admin option on role "regress_role_normal"
+ERROR: permission denied
+DETAIL: You must have ADMIN OPTION on role "regress_role_normal".
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
--
2.25.1
On Fri, Jan 27, 2023 at 03:15:07PM -0800, Nathan Bossart wrote:
One thing that feels a bit odd is how some of the DETAILs mention the
operation being attempted while others do not. For example, we haveERROR: permission denied to drop role
DETAIL: You must have SUPERUSER privilege to drop roles with SUPERUSER.In this case, the DETAIL explains the action that is prohibited. In other
cases, we have something likeERROR: permission denied to alter role
DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "myrole".which does not. I think this is okay because adding "to alter the role" to
the end of the DETAIL seems kind of awkward. But in other cases, such asERROR: permission denied to use replication slots
DETAIL: You must have REPLICATION privilege.adding the operation to the end seems less awkward (i.e., "You must have
REPLICATION privilege to use replication slots."). I don't think there's
any information lost by omitting the action in the DETAIL, so perhaps this
is just a stylistic choice. I think I'm inclined to add the action to the
DETAIL whenever it doesn't make the message lengthy and awkward, and leave
it out otherwise. Thoughts?
Here is a new patch set with this change and some other light editing.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v5-0001-Improve-user.c-error-messages.patchtext/x-diff; charset=us-asciiDownload
From 196c52841d19d507d695122ac5e9ecab45a908f2 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 26 Jan 2023 11:05:13 -0800
Subject: [PATCH v5 1/2] Improve user.c error messages.
---
src/backend/commands/user.c | 169 ++++++++++++++++------
src/test/regress/expected/create_role.out | 77 ++++++----
src/test/regress/expected/dependency.out | 4 +
src/test/regress/expected/privileges.out | 23 ++-
4 files changed, 198 insertions(+), 75 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 3a92e930c0..3d0b8b9ea6 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -316,23 +316,33 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles.",
+ "CREATEROLE")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create superusers")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb permission to create createdb users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "CREATEDB", "CREATEDB")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication permission to create replication users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "REPLICATION", "REPLICATION")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls to create bypassrls users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have %s privilege to create roles with %s.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/*
@@ -744,10 +754,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
roleid = authform->oid;
/* To mess with a superuser in any way you gotta be superuser. */
- if (!superuser() && (authform->rolsuper || dissuper))
+ if (!superuser() && authform->rolsuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ if (!superuser() && dissuper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "SUPERUSER", "SUPERUSER")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -761,13 +779,17 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
dvalidUntil || disreplication || dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", rolename)));
/* an unprivileged user can change their own password */
if (dpassword && roleid != currentUserId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errdetail("To change another role's password, you must have %s privilege and %s on the role.",
+ "CREATEROLE", "ADMIN OPTION")));
}
else if (!superuser())
{
@@ -779,23 +801,30 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb privilege to change createdb attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "CREATEDB", "CREATEDB")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication privilege to change replication attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "REPLICATION", "REPLICATION")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to change the %s attribute.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\" to add members",
- rolename)));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s on role \"%s\" to add members.",
+ "ADMIN OPTION", rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
@@ -837,8 +866,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: bootstrap user must be superuser")));
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("permission denied to alter role"),
+ errdetail("The bootstrap user must have %s privilege.",
+ "SUPERUSER")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
@@ -999,7 +1030,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1008,7 +1041,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATROLE", "ADMIN OPTION", NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
@@ -1038,7 +1073,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter settings globally")));
+ errmsg("permission denied to alter setting"),
+ errdetail("You must have %s privilege to alter settings globally.",
+ "SUPERUSER")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1061,7 +1098,9 @@ DropRole(DropRoleStmt *stmt)
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop role")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege and %s on the target roles.",
+ "CREATEROLE", "ADMIN OPTION")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
@@ -1131,12 +1170,15 @@ DropRole(DropRoleStmt *stmt)
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to drop superusers")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege to drop roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
@@ -1378,12 +1420,14 @@ RenameRole(const char *oldname, const char *newname)
* Only superusers can mess with superusers. Otherwise, a user with
* CREATEROLE can rename a role for which they have ADMIN OPTION.
*/
- if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+ if (authform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to rename superusers")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have %s privilege to rename roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1391,7 +1435,9 @@ RenameRole(const char *oldname, const char *newname)
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to rename role")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have %s privilege and %s on role \"%s\".",
+ "CREATEROLE", "ADMIN OPTION", NameStr(authform->rolname))));
}
/* OK, construct the modified tuple */
@@ -1554,7 +1600,9 @@ DropOwnedObjects(DropOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop objects")));
+ errmsg("permission denied to drop objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Ok, do it */
@@ -1581,7 +1629,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
@@ -1590,7 +1640,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
@@ -1738,7 +1790,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s cannot be granted back to your own grantor",
+ "ADMIN OPTION")));
plan_member_revoke(memlist, actions, memberid);
}
@@ -1763,7 +1816,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s cannot be granted back to your own grantor",
+ "ADMIN OPTION")));
ReleaseSysCacheList(memlist);
}
@@ -2081,9 +2135,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s privilege to grant roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s privilege to revoke roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ }
}
else
{
@@ -2091,10 +2158,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(roleid, false))));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
+ }
}
}
@@ -2173,14 +2252,18 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("grantor must have ADMIN OPTION on \"%s\"",
- GetUserNameFromId(roleid, false))));
+ errmsg("permission denied to grant privileges as role \"%s\"",
+ GetUserNameFromId(grantorId, false)),
+ errdetail("The grantor must have %s on role \"%s\".",
+ "ADMIN OPTION", GetUserNameFromId(roleid, false))));
}
else
{
@@ -2188,7 +2271,9 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must have privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
}
/*
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 9f431bd4f5..2a7d14dba9 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -7,26 +7,35 @@ CREATE ROLE regress_role_normal;
-- fail, CREATEROLE user can't give away role attributes without having them
SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
-ERROR: must be superuser to create superusers
+ERROR: permission denied to create role
+DETAIL: You must have SUPERUSER privilege to create roles with SUPERUSER.
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have REPLICATION privilege to create roles with REPLICATION.
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must have bypassrls to create bypassrls users
+ERROR: permission denied to create role
+DETAIL: You must have BYPASSRLS privilege to create roles with BYPASSRLS.
CREATE ROLE regress_nosuch_createdb CREATEDB;
-ERROR: must have createdb permission to create createdb users
+ERROR: permission denied to create role
+DETAIL: You must have CREATEDB privilege to create roles with CREATEDB.
-- ok, can create a role without any special attributes
CREATE ROLE regress_role_limited;
-- fail, can't give it in any of the restricted attributes
ALTER ROLE regress_role_limited SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
ALTER ROLE regress_role_limited REPLICATION;
-ERROR: must have replication privilege to change replication attribute
+ERROR: permission denied to alter role
+DETAIL: You must have REPLICATION privilege to change the REPLICATION attribute.
ALTER ROLE regress_role_limited CREATEDB;
-ERROR: must have createdb privilege to change createdb attribute
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEDB privilege to change the CREATEDB attribute.
ALTER ROLE regress_role_limited BYPASSRLS;
-ERROR: must have bypassrls privilege to change bypassrls attribute
+ERROR: permission denied to alter role
+DETAIL: You must have BYPASSRLS privilege to change the BYPASSRLS attribute.
DROP ROLE regress_role_limited;
-- ok, can give away these role attributes if you have them
SET SESSION AUTHORIZATION regress_role_admin;
@@ -43,9 +52,11 @@ ALTER ROLE regress_createdb NOCREATEDB;
ALTER ROLE regress_createdb CREATEDB;
-- fail, can't toggle SUPERUSER
ALTER ROLE regress_createdb SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
ALTER ROLE regress_createdb NOSUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have SUPERUSER privilege to change the SUPERUSER attribute.
-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
@@ -59,7 +70,8 @@ CREATE ROLE regress_noiseword SYSID 12345;
NOTICE: SYSID can no longer be specified
-- fail, cannot grant membership in superuser role
CREATE ROLE regress_nosuch_super IN ROLE regress_role_super;
-ERROR: must be superuser to alter superusers
+ERROR: permission denied to grant role "regress_role_super"
+DETAIL: You must have SUPERUSER privilege to grant roles with SUPERUSER.
-- fail, database owner cannot have members
CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner;
ERROR: role "pg_database_owner" cannot have explicit members
@@ -97,8 +109,10 @@ COMMENT ON ROLE regress_role_normal IS 'some comment';
ERROR: must have admin option on role "regress_role_normal"
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
ALTER ROLE regress_role_normal NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-ERROR: permission denied
+ERROR: permission denied to alter role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
@@ -123,6 +137,7 @@ ERROR: must be able to SET ROLE "regress_tenant"
-- fail, we don't inherit permissions from regress_tenant
REASSIGN OWNED BY regress_tenant TO regress_createrole;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_tenant".
-- ok, create a role with a value for createrole_self_grant
SET createrole_self_grant = 'set, inherit';
CREATE ROLE regress_tenant2;
@@ -150,25 +165,35 @@ ERROR: must be able to SET ROLE "regress_tenant2"
DROP TABLE tenant2_table;
-- fail, CREATEROLE is not enough to create roles in privileged roles
CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data;
-ERROR: must have admin option on role "pg_read_all_data"
+ERROR: permission denied to grant role "pg_read_all_data"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_data".
CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data;
-ERROR: must have admin option on role "pg_write_all_data"
+ERROR: permission denied to grant role "pg_write_all_data"
+DETAIL: You must have ADMIN OPTION on role "pg_write_all_data".
CREATE ROLE regress_monitor IN ROLE pg_monitor;
-ERROR: must have admin option on role "pg_monitor"
+ERROR: permission denied to grant role "pg_monitor"
+DETAIL: You must have ADMIN OPTION on role "pg_monitor".
CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings;
-ERROR: must have admin option on role "pg_read_all_settings"
+ERROR: permission denied to grant role "pg_read_all_settings"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_settings".
CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats;
-ERROR: must have admin option on role "pg_read_all_stats"
+ERROR: permission denied to grant role "pg_read_all_stats"
+DETAIL: You must have ADMIN OPTION on role "pg_read_all_stats".
CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables;
-ERROR: must have admin option on role "pg_stat_scan_tables"
+ERROR: permission denied to grant role "pg_stat_scan_tables"
+DETAIL: You must have ADMIN OPTION on role "pg_stat_scan_tables".
CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files;
-ERROR: must have admin option on role "pg_read_server_files"
+ERROR: permission denied to grant role "pg_read_server_files"
+DETAIL: You must have ADMIN OPTION on role "pg_read_server_files".
CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files;
-ERROR: must have admin option on role "pg_write_server_files"
+ERROR: permission denied to grant role "pg_write_server_files"
+DETAIL: You must have ADMIN OPTION on role "pg_write_server_files".
CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program;
-ERROR: must have admin option on role "pg_execute_server_program"
+ERROR: permission denied to grant role "pg_execute_server_program"
+DETAIL: You must have ADMIN OPTION on role "pg_execute_server_program".
CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend;
-ERROR: must have admin option on role "pg_signal_backend"
+ERROR: permission denied to grant role "pg_signal_backend"
+DETAIL: You must have ADMIN OPTION on role "pg_signal_backend".
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
@@ -211,11 +236,13 @@ DROP ROLE regress_inroles;
DROP ROLE regress_adminroles;
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
DROP ROLE regress_role_super;
-ERROR: must be superuser to drop superusers
+ERROR: permission denied to drop role
+DETAIL: You must have SUPERUSER privilege to drop roles with SUPERUSER.
DROP ROLE regress_role_admin;
ERROR: current user cannot be dropped
DROP ROLE regress_rolecreator;
-ERROR: must have admin option on role "regress_rolecreator"
+ERROR: permission denied to drop role
+DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_rolecreator".
-- ok
RESET SESSION AUTHORIZATION;
REVOKE CREATE ON DATABASE regression FROM regress_role_admin CASCADE;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 520035f6a0..78ee4d7448 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -48,12 +48,16 @@ SET SESSION AUTHORIZATION regress_dep_user0;
-- permission denied
DROP OWNED BY regress_dep_user1;
ERROR: permission denied to drop objects
+DETAIL: You must have privileges of role "regress_dep_user1".
DROP OWNED BY regress_dep_user0, regress_dep_user2;
ERROR: permission denied to drop objects
+DETAIL: You must have privileges of role "regress_dep_user2".
REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_dep_user1".
REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
ERROR: permission denied to reassign objects
+DETAIL: You must have privileges of role "regress_dep_user1".
-- this one is allowed
DROP OWNED BY regress_dep_user0;
CREATE TABLE deptest1 (f1 int unique);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..6794645e56 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -37,7 +37,7 @@ CREATE ROLE regress_priv_role;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3;
-ERROR: admin option cannot be granted back to your own grantor
+ERROR: ADMIN OPTION cannot be granted back to your own grantor
-- need CASCADE to revoke grant or admin option if dependent grants exist
REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail
ERROR: dependent privileges exist
@@ -156,7 +156,8 @@ ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate
NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1"
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted
-ERROR: must have CREATEROLE privilege to change another user's password
+ERROR: permission denied to alter role
+DETAIL: To change another role's password, you must have CREATEROLE privilege and ADMIN OPTION on the role.
RESET SESSION AUTHORIZATION;
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1;
@@ -168,7 +169,8 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1;
-- test owner privileges
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION
-ERROR: grantor must have ADMIN OPTION on "regress_priv_role"
+ERROR: permission denied to grant privileges as role "regress_priv_role"
+DETAIL: The grantor must have ADMIN OPTION on role "regress_priv_role".
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE;
REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error
ERROR: role "foo" does not exist
@@ -1795,7 +1797,8 @@ REFRESH MATERIALIZED VIEW sro_mv;
ERROR: cannot fire deferred trigger within security-restricted operation
CONTEXT: SQL function "mv_action" statement 1
BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
CONTEXT: SQL function "unwanted_grant" statement 1
SQL statement "SELECT unwanted_grant()"
PL/pgSQL function sro_trojan() line 1 at PERFORM
@@ -1825,10 +1828,12 @@ CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4"
dogrant_ok
@@ -1838,10 +1843,12 @@ NOTICE: role "regress_priv_user5" has already been granted membership in role "
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have ADMIN OPTION on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user4;
DROP FUNCTION dogrant_ok();
REVOKE regress_priv_group2 FROM regress_priv_user5;
--
2.25.1
v5-0002-Improve-more-insufficient-privileges-error-messag.patchtext/x-diff; charset=us-asciiDownload
From 7ea85df2ab6a5f9224d0cbf1b11f9323e4ce7de1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 14:52:57 -0800
Subject: [PATCH v5 2/2] Improve more insufficient-privileges error messages.
---
contrib/file_fdw/expected/file_fdw.out | 3 ++-
contrib/file_fdw/file_fdw.c | 8 ++++++--
contrib/test_decoding/expected/permissions.out | 12 ++++++++----
src/backend/backup/basebackup_server.c | 4 +++-
src/backend/catalog/objectaddress.c | 16 +++++++++++-----
src/backend/commands/copy.c | 12 +++++++++---
src/backend/replication/slot.c | 5 +++--
src/backend/storage/ipc/procarray.c | 4 +++-
src/backend/storage/ipc/signalfuncs.c | 16 ++++++++++++----
src/backend/tcop/utility.c | 5 ++++-
src/backend/utils/init/miscinit.c | 4 ++++
src/backend/utils/init/postinit.c | 8 +++++---
src/backend/utils/misc/guc.c | 15 +++++++++------
.../dummy_seclabel/expected/dummy_seclabel.out | 3 ++-
.../modules/unsafe_tests/expected/rolenames.out | 3 ++-
src/test/regress/expected/create_role.out | 3 ++-
16 files changed, 85 insertions(+), 36 deletions(-)
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 36d76ba26c..f67a33dace 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -474,7 +474,8 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: permission denied to set the "filename" option of a file_fdw foreign table
+DETAIL: You must have privileges of the "pg_read_server_files" role to set this option.
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 8ccc167548..b00fd0a5c7 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -278,13 +278,17 @@ file_fdw_validator(PG_FUNCTION_ARGS)
!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("permission denied to set the \"filename\" option of a file_fdw foreign table"),
+ errdetail("You must have privileges of the \"%s\" role to set this option.",
+ "pg_read_server_files")));
if (strcmp(def->defname, "program") == 0 &&
!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("permission denied to set the \"program\" option of a file_fdw foreign table"),
+ errdetail("You must have privileges of the \"%s\" role to set this option.",
+ "pg_execute_server_program")));
filename = defGetString(def);
}
diff --git a/contrib/test_decoding/expected/permissions.out b/contrib/test_decoding/expected/permissions.out
index ed97f81dda..5a0bdab326 100644
--- a/contrib/test_decoding/expected/permissions.out
+++ b/contrib/test_decoding/expected/permissions.out
@@ -54,13 +54,16 @@ RESET ROLE;
-- plain user *can't* can control replication
SET ROLE regress_lr_normal;
SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have REPLICATION privilege to use replication slots.
INSERT INTO lr_test VALUES('lr_superuser_init');
ERROR: permission denied for table lr_test
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have REPLICATION privilege to use replication slots.
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have REPLICATION privilege to use replication slots.
RESET ROLE;
-- replication users can drop superuser created slots
SET ROLE regress_lr_superuser;
@@ -90,7 +93,8 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_d
RESET ROLE;
SET ROLE regress_lr_normal;
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have REPLICATION privilege to use replication slots.
RESET ROLE;
-- all users can see existing slots
SET ROLE regress_lr_superuser;
diff --git a/src/backend/backup/basebackup_server.c b/src/backend/backup/basebackup_server.c
index 0258d7a03b..de89df190f 100644
--- a/src/backend/backup/basebackup_server.c
+++ b/src/backend/backup/basebackup_server.c
@@ -72,7 +72,9 @@ bbsink_server_new(bbsink *next, char *pathname)
if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a role with privileges of the pg_write_server_files role to create backup stored on server")));
+ errmsg("permission denied to create backup stored on server"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_write_server_files")));
CommitTransactionCommand();
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 25c50d66fd..2a39ce0567 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2547,20 +2547,26 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
if (!superuser_arg(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser")));
+ errmsg("permission denied"),
+ errdetail("You must have %s privilege.",
+ "SUPERUSER")));
}
else
{
if (!has_createrole_privilege(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege")));
+ errmsg("permission denied"),
+ errdetail("You must have %s privilege.",
+ "CREATEROLE")));
if (!is_admin_of_role(roleid, address.objectId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(address.objectId,
- true))));
+ errmsg("permission denied"),
+ errdetail("You must have %s on role \"%s\".",
+ "ADMIN OPTION",
+ GetUserNameFromId(address.objectId,
+ true))));
}
break;
case OBJECT_TSPARSER:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e34f583ea7..e557eb17b6 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -83,7 +83,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("permission denied to COPY to or from an external program"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_execute_server_program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
@@ -92,14 +94,18 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
+ errmsg("permission denied to COPY from a file"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_read_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
+ errmsg("permission denied to COPY to a file"),
+ errdetail("You must have privileges of the \"%s\" role.",
+ "pg_write_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index f286918f69..699005bd04 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1140,10 +1140,11 @@ CheckSlotRequirements(void)
void
CheckSlotPermissions(void)
{
- if (!superuser() && !has_rolreplication(GetUserId()))
+ if (!has_rolreplication(GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to use replication slots")));
+ errmsg("permission denied to use replication slots"),
+ errdetail("You must have %s privilege to use replication slots.", "REPLICATION")));
}
/*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a7071b2fce..4086938d28 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3860,7 +3860,9 @@ TerminateOtherDBBackends(Oid databaseId)
!has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must have privileges of the role whose process is being terminated or have privileges of the \"%s\" role.",
+ "pg_signal_backend")));
}
}
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index bc93ab5b52..7e0f0aef73 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -121,12 +121,16 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to cancel superuser query")));
+ errmsg("permission denied to cancel query"),
+ errdetail("You must have %s privilege to cancel queries of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend")));
+ errmsg("permission denied to cancel query"),
+ errdetail("You must have privileges of the role whose query is being canceled or have privileges of the \"%s\" role.",
+ "pg_signal_backend")));
PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
}
@@ -223,12 +227,16 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to terminate superuser process")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must have %s privilege to terminate processes of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must have privileges of the role whose process is being terminated or have privileges of the \"%s\" role.",
+ "pg_signal_backend")));
/* Wait only on success and if actually requested */
if (r == SIGNAL_BACKEND_SUCCESS && timeout > 0)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c7d9d96b45..0ff9aad106 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -950,7 +950,10 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_checkpoint to do CHECKPOINT")));
+ errmsg("permission denied to execute %s command",
+ "CHECKPOINT"),
+ errdetail("You must have privileges of the \"%s\" role to execute this command.",
+ "pg_checkpoint")));
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 59532bbd80..b0cfbc742b 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -701,6 +701,10 @@ has_rolreplication(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))
{
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 2f07ca7a0e..269712215c 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -945,7 +945,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_connections")));
+ errmsg("remaining connection slots are reserved for roles with privileges of \"%s\"",
+ "pg_use_reserved_connections")));
}
/* Check replication permissions needed for walsender processes. */
@@ -953,10 +954,11 @@ InitPostgres(const char *in_dbname, Oid dboid,
{
Assert(!bootstrap);
- if (!superuser() && !has_rolreplication(GetUserId()))
+ if (!has_rolreplication(GetUserId()))
ereport(FATAL,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to start walsender")));
+ errmsg("permission denied to start WAL sender"),
+ errdetail("You must have %s privilege to start a WAL sender process.", "REPLICATION")));
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index bc83f3577d..d1aa464571 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4208,8 +4208,9 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must have privileges of the \"%s\" role to examine this parameter.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -4254,8 +4255,9 @@ GetConfigOptionResetString(const char *name)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must have privileges of the \"%s\" role to examine this parameter.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -5260,8 +5262,9 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must have privileges of the \"%s\" role to examine this parameter.",
+ "pg_read_all_settings")));
if (varname)
*varname = record->name;
diff --git a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
index c57d4fd2df..abb0b2fa0f 100644
--- a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
+++ b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
@@ -59,7 +59,8 @@ SECURITY LABEL ON ROLE regress_dummy_seclabel_user4 IS 'unclassified'; -- fail (
ERROR: role "regress_dummy_seclabel_user4" does not exist
SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
SECURITY LABEL ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- fail (not privileged)
-ERROR: must have CREATEROLE privilege
+ERROR: permission denied
+DETAIL: You must have CREATEROLE privilege.
RESET SESSION AUTHORIZATION;
--
-- Test for various types of object
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index 88b1ff843b..98029cab01 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,8 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: permission denied to examine "session_preload_libraries"
+DETAIL: You must have privileges of the "pg_read_all_settings" role to examine this parameter.
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 2a7d14dba9..d9620fb8d7 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -106,7 +106,8 @@ ALTER ROLE regress_hasprivs RENAME TO regress_tenant;
ALTER ROLE regress_tenant NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-- fail, we should be unable to modify a role we did not create
COMMENT ON ROLE regress_role_normal IS 'some comment';
-ERROR: must have admin option on role "regress_role_normal"
+ERROR: permission denied
+DETAIL: You must have ADMIN OPTION on role "regress_role_normal".
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
DETAIL: You must have CREATEROLE privilege and ADMIN OPTION on role "regress_role_normal".
--
2.25.1
On 07.02.23 21:10, Nathan Bossart wrote:
ERROR: permission denied to use replication slots
DETAIL: You must have REPLICATION privilege.adding the operation to the end seems less awkward (i.e., "You must have
REPLICATION privilege to use replication slots."). I don't think there's
any information lost by omitting the action in the DETAIL, so perhaps this
is just a stylistic choice. I think I'm inclined to add the action to the
DETAIL whenever it doesn't make the message lengthy and awkward, and leave
it out otherwise. Thoughts?Here is a new patch set with this change and some other light editing.
I'm concerned about the loose use of "privilege" here. A privilege is
something I can grant. So if someone doesn't have the "REPLICATION
privilege", as in the above example, I would expect to be able to do
"GRANT REPLICATION TO someuser". Since that is not what is happening,
we should use some other term. The documentation around CREATE USER
uses the terms "attribute" and "option" (and also "privilege") for these
things.
Similarly -- this is an existing issue but we might as well look at it
-- in something like
must be superuser or a role with privileges of the
pg_write_server_files role
the phrase "a role with the privileges of that other role" seems
ambiguous. Doesn't it really mean you must be a member of that role?
I also feel that in sentences like
"You must have %s privilege to create roles."
a "the" is missing.
On Mon, Feb 20, 2023 at 08:54:48AM +0100, Peter Eisentraut wrote:
I'm concerned about the loose use of "privilege" here. A privilege is
something I can grant. So if someone doesn't have the "REPLICATION
privilege", as in the above example, I would expect to be able to do "GRANT
REPLICATION TO someuser". Since that is not what is happening, we should
use some other term. The documentation around CREATE USER uses the terms
"attribute" and "option" (and also "privilege") for these things.
Good point. I will adjust these to use "attribute" instead.
Similarly -- this is an existing issue but we might as well look at it -- in
something likemust be superuser or a role with privileges of the
pg_write_server_files rolethe phrase "a role with the privileges of that other role" seems ambiguous.
Doesn't it really mean you must be a member of that role?
Membership alone is not sufficient. You must also inherit the privileges
of the role via the INHERIT option. I thought about making this something
like
must have the INHERIT option on role %s
but I'm not sure that's accurate either. That wording makes it sound lіke
you need to be granted membership to the role directly WITH INHERIT OPTION,
but what you really need is membership, direct or indirect, with an INHERIT
chain up to the role in question. However, it looks like "must have the
ADMIN option on role %s" is used to mean something similar, so perhaps I am
overthinking it.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Feb 20, 2023 at 11:02:10AM -0800, Nathan Bossart wrote:
On Mon, Feb 20, 2023 at 08:54:48AM +0100, Peter Eisentraut wrote:
I'm concerned about the loose use of "privilege" here. A privilege is
something I can grant. So if someone doesn't have the "REPLICATION
privilege", as in the above example, I would expect to be able to do "GRANT
REPLICATION TO someuser". Since that is not what is happening, we should
use some other term. The documentation around CREATE USER uses the terms
"attribute" and "option" (and also "privilege") for these things.Good point. I will adjust these to use "attribute" instead.
done in v6
Similarly -- this is an existing issue but we might as well look at it -- in
something likemust be superuser or a role with privileges of the
pg_write_server_files rolethe phrase "a role with the privileges of that other role" seems ambiguous.
Doesn't it really mean you must be a member of that role?Membership alone is not sufficient. You must also inherit the privileges
of the role via the INHERIT option. I thought about making this something
likemust have the INHERIT option on role %s
but I'm not sure that's accurate either. That wording makes it sound lіke
you need to be granted membership to the role directly WITH INHERIT OPTION,
but what you really need is membership, direct or indirect, with an INHERIT
chain up to the role in question. However, it looks like "must have the
ADMIN option on role %s" is used to mean something similar, so perhaps I am
overthinking it.
For now, I've reworded these as "must inherit privileges of".
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v6-0001-Improve-user.c-error-messages.patchtext/x-diff; charset=us-asciiDownload
From cc6131da14200f32778ca8ba9628f86dccdbd43d Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 26 Jan 2023 11:05:13 -0800
Subject: [PATCH v6 1/2] Improve user.c error messages.
---
src/backend/commands/user.c | 169 ++++++++++++++++------
src/test/regress/expected/create_role.out | 77 ++++++----
src/test/regress/expected/dependency.out | 4 +
src/test/regress/expected/privileges.out | 23 ++-
4 files changed, 198 insertions(+), 75 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 3a92e930c0..e92bb656f9 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -316,23 +316,33 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have the %s attribute to create roles.",
+ "CREATEROLE")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create superusers")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have the %s attribute to create roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb permission to create createdb users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have the %s attribute to create roles with %s.",
+ "CREATEDB", "CREATEDB")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication permission to create replication users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have the %s attribute to create roles with %s.",
+ "REPLICATION", "REPLICATION")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls to create bypassrls users")));
+ errmsg("permission denied to create role"),
+ errdetail("You must have the %s attribute to create roles with %s.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/*
@@ -744,10 +754,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
roleid = authform->oid;
/* To mess with a superuser in any way you gotta be superuser. */
- if (!superuser() && (authform->rolsuper || dissuper))
+ if (!superuser() && authform->rolsuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s attribute to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ if (!superuser() && dissuper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s attribute to change the %s attribute.",
+ "SUPERUSER", "SUPERUSER")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -761,13 +779,17 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
dvalidUntil || disreplication || dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s attribute and the %s option on role \"%s\".",
+ "CREATEROLE", "ADMIN", rolename)));
/* an unprivileged user can change their own password */
if (dpassword && roleid != currentUserId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errdetail("To change another role's password, you must have the %s attribute and the %s option on the role.",
+ "CREATEROLE", "ADMIN")));
}
else if (!superuser())
{
@@ -779,23 +801,30 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb privilege to change createdb attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s attribute to change the %s attribute.",
+ "CREATEDB", "CREATEDB")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication privilege to change replication attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s attribute to change the %s attribute.",
+ "REPLICATION", "REPLICATION")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s attribute to change the %s attribute.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\" to add members",
- rolename)));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s option on role \"%s\" to add members.",
+ "ADMIN", rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
@@ -837,8 +866,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: bootstrap user must be superuser")));
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("permission denied to alter role"),
+ errdetail("The bootstrap user must have the %s attribute.",
+ "SUPERUSER")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
@@ -999,7 +1030,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s attribute to alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1008,7 +1041,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("You must have the %s attribute and the %s option on role \"%s\".",
+ "CREATROLE", "ADMIN", NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
@@ -1038,7 +1073,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter settings globally")));
+ errmsg("permission denied to alter setting"),
+ errdetail("You must have the %s attribute to alter settings globally.",
+ "SUPERUSER")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1061,7 +1098,9 @@ DropRole(DropRoleStmt *stmt)
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop role")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have the %s attribute and the %s option on the target roles.",
+ "CREATEROLE", "ADMIN")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
@@ -1131,12 +1170,15 @@ DropRole(DropRoleStmt *stmt)
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to drop superusers")));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have the %s attribute to drop roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errdetail("You must have the %s attribute and the %s option on role \"%s\".",
+ "CREATEROLE", "ADMIN", NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
@@ -1378,12 +1420,14 @@ RenameRole(const char *oldname, const char *newname)
* Only superusers can mess with superusers. Otherwise, a user with
* CREATEROLE can rename a role for which they have ADMIN OPTION.
*/
- if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+ if (authform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to rename superusers")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have the %s attribute to rename roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1391,7 +1435,9 @@ RenameRole(const char *oldname, const char *newname)
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to rename role")));
+ errmsg("permission denied to rename role"),
+ errdetail("You must have the %s attribute and the %s option on role \"%s\".",
+ "CREATEROLE", "ADMIN", NameStr(authform->rolname))));
}
/* OK, construct the modified tuple */
@@ -1554,7 +1600,9 @@ DropOwnedObjects(DropOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop objects")));
+ errmsg("permission denied to drop objects"),
+ errdetail("You must inherit privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Ok, do it */
@@ -1581,7 +1629,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must inherit privileges of role \"%s\".",
+ GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
@@ -1590,7 +1640,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("You must inherit privileges role \"%s\".",
+ GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
@@ -1738,7 +1790,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s option cannot be granted back to your own grantor",
+ "ADMIN")));
plan_member_revoke(memlist, actions, memberid);
}
@@ -1763,7 +1816,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s option cannot be granted back to your own grantor",
+ "ADMIN")));
ReleaseSysCacheList(memlist);
}
@@ -2081,9 +2135,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have the %s attribute to grant roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have the %s attribute to revoke roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ }
}
else
{
@@ -2091,10 +2158,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(roleid, false))));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have the %s option on role \"%s\".",
+ "ADMIN", GetUserNameFromId(roleid, false))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("You must have the %s option on role \"%s\".",
+ "ADMIN", GetUserNameFromId(roleid, false))));
+ }
}
}
@@ -2173,14 +2252,18 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must inherit privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("grantor must have ADMIN OPTION on \"%s\"",
- GetUserNameFromId(roleid, false))));
+ errmsg("permission denied to grant privileges as role \"%s\"",
+ GetUserNameFromId(grantorId, false)),
+ errdetail("The grantor must have the %s option on role \"%s\".",
+ "ADMIN", GetUserNameFromId(roleid, false))));
}
else
{
@@ -2188,7 +2271,9 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("You must inherit privileges of role \"%s\".",
+ GetUserNameFromId(grantorId, false))));
}
/*
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 9f431bd4f5..7157d4c589 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -7,26 +7,35 @@ CREATE ROLE regress_role_normal;
-- fail, CREATEROLE user can't give away role attributes without having them
SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
-ERROR: must be superuser to create superusers
+ERROR: permission denied to create role
+DETAIL: You must have the SUPERUSER attribute to create roles with SUPERUSER.
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have the REPLICATION attribute to create roles with REPLICATION.
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: You must have the REPLICATION attribute to create roles with REPLICATION.
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must have bypassrls to create bypassrls users
+ERROR: permission denied to create role
+DETAIL: You must have the BYPASSRLS attribute to create roles with BYPASSRLS.
CREATE ROLE regress_nosuch_createdb CREATEDB;
-ERROR: must have createdb permission to create createdb users
+ERROR: permission denied to create role
+DETAIL: You must have the CREATEDB attribute to create roles with CREATEDB.
-- ok, can create a role without any special attributes
CREATE ROLE regress_role_limited;
-- fail, can't give it in any of the restricted attributes
ALTER ROLE regress_role_limited SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have the SUPERUSER attribute to change the SUPERUSER attribute.
ALTER ROLE regress_role_limited REPLICATION;
-ERROR: must have replication privilege to change replication attribute
+ERROR: permission denied to alter role
+DETAIL: You must have the REPLICATION attribute to change the REPLICATION attribute.
ALTER ROLE regress_role_limited CREATEDB;
-ERROR: must have createdb privilege to change createdb attribute
+ERROR: permission denied to alter role
+DETAIL: You must have the CREATEDB attribute to change the CREATEDB attribute.
ALTER ROLE regress_role_limited BYPASSRLS;
-ERROR: must have bypassrls privilege to change bypassrls attribute
+ERROR: permission denied to alter role
+DETAIL: You must have the BYPASSRLS attribute to change the BYPASSRLS attribute.
DROP ROLE regress_role_limited;
-- ok, can give away these role attributes if you have them
SET SESSION AUTHORIZATION regress_role_admin;
@@ -43,9 +52,11 @@ ALTER ROLE regress_createdb NOCREATEDB;
ALTER ROLE regress_createdb CREATEDB;
-- fail, can't toggle SUPERUSER
ALTER ROLE regress_createdb SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have the SUPERUSER attribute to change the SUPERUSER attribute.
ALTER ROLE regress_createdb NOSUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: You must have the SUPERUSER attribute to change the SUPERUSER attribute.
-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
@@ -59,7 +70,8 @@ CREATE ROLE regress_noiseword SYSID 12345;
NOTICE: SYSID can no longer be specified
-- fail, cannot grant membership in superuser role
CREATE ROLE regress_nosuch_super IN ROLE regress_role_super;
-ERROR: must be superuser to alter superusers
+ERROR: permission denied to grant role "regress_role_super"
+DETAIL: You must have the SUPERUSER attribute to grant roles with SUPERUSER.
-- fail, database owner cannot have members
CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner;
ERROR: role "pg_database_owner" cannot have explicit members
@@ -97,8 +109,10 @@ COMMENT ON ROLE regress_role_normal IS 'some comment';
ERROR: must have admin option on role "regress_role_normal"
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
+DETAIL: You must have the CREATEROLE attribute and the ADMIN option on role "regress_role_normal".
ALTER ROLE regress_role_normal NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-ERROR: permission denied
+ERROR: permission denied to alter role
+DETAIL: You must have the CREATEROLE attribute and the ADMIN option on role "regress_role_normal".
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
@@ -123,6 +137,7 @@ ERROR: must be able to SET ROLE "regress_tenant"
-- fail, we don't inherit permissions from regress_tenant
REASSIGN OWNED BY regress_tenant TO regress_createrole;
ERROR: permission denied to reassign objects
+DETAIL: You must inherit privileges of role "regress_tenant".
-- ok, create a role with a value for createrole_self_grant
SET createrole_self_grant = 'set, inherit';
CREATE ROLE regress_tenant2;
@@ -150,25 +165,35 @@ ERROR: must be able to SET ROLE "regress_tenant2"
DROP TABLE tenant2_table;
-- fail, CREATEROLE is not enough to create roles in privileged roles
CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data;
-ERROR: must have admin option on role "pg_read_all_data"
+ERROR: permission denied to grant role "pg_read_all_data"
+DETAIL: You must have the ADMIN option on role "pg_read_all_data".
CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data;
-ERROR: must have admin option on role "pg_write_all_data"
+ERROR: permission denied to grant role "pg_write_all_data"
+DETAIL: You must have the ADMIN option on role "pg_write_all_data".
CREATE ROLE regress_monitor IN ROLE pg_monitor;
-ERROR: must have admin option on role "pg_monitor"
+ERROR: permission denied to grant role "pg_monitor"
+DETAIL: You must have the ADMIN option on role "pg_monitor".
CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings;
-ERROR: must have admin option on role "pg_read_all_settings"
+ERROR: permission denied to grant role "pg_read_all_settings"
+DETAIL: You must have the ADMIN option on role "pg_read_all_settings".
CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats;
-ERROR: must have admin option on role "pg_read_all_stats"
+ERROR: permission denied to grant role "pg_read_all_stats"
+DETAIL: You must have the ADMIN option on role "pg_read_all_stats".
CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables;
-ERROR: must have admin option on role "pg_stat_scan_tables"
+ERROR: permission denied to grant role "pg_stat_scan_tables"
+DETAIL: You must have the ADMIN option on role "pg_stat_scan_tables".
CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files;
-ERROR: must have admin option on role "pg_read_server_files"
+ERROR: permission denied to grant role "pg_read_server_files"
+DETAIL: You must have the ADMIN option on role "pg_read_server_files".
CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files;
-ERROR: must have admin option on role "pg_write_server_files"
+ERROR: permission denied to grant role "pg_write_server_files"
+DETAIL: You must have the ADMIN option on role "pg_write_server_files".
CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program;
-ERROR: must have admin option on role "pg_execute_server_program"
+ERROR: permission denied to grant role "pg_execute_server_program"
+DETAIL: You must have the ADMIN option on role "pg_execute_server_program".
CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend;
-ERROR: must have admin option on role "pg_signal_backend"
+ERROR: permission denied to grant role "pg_signal_backend"
+DETAIL: You must have the ADMIN option on role "pg_signal_backend".
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
@@ -211,11 +236,13 @@ DROP ROLE regress_inroles;
DROP ROLE regress_adminroles;
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
DROP ROLE regress_role_super;
-ERROR: must be superuser to drop superusers
+ERROR: permission denied to drop role
+DETAIL: You must have the SUPERUSER attribute to drop roles with SUPERUSER.
DROP ROLE regress_role_admin;
ERROR: current user cannot be dropped
DROP ROLE regress_rolecreator;
-ERROR: must have admin option on role "regress_rolecreator"
+ERROR: permission denied to drop role
+DETAIL: You must have the CREATEROLE attribute and the ADMIN option on role "regress_rolecreator".
-- ok
RESET SESSION AUTHORIZATION;
REVOKE CREATE ON DATABASE regression FROM regress_role_admin CASCADE;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 520035f6a0..d1e8dcdf5c 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -48,12 +48,16 @@ SET SESSION AUTHORIZATION regress_dep_user0;
-- permission denied
DROP OWNED BY regress_dep_user1;
ERROR: permission denied to drop objects
+DETAIL: You must inherit privileges of role "regress_dep_user1".
DROP OWNED BY regress_dep_user0, regress_dep_user2;
ERROR: permission denied to drop objects
+DETAIL: You must inherit privileges of role "regress_dep_user2".
REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
ERROR: permission denied to reassign objects
+DETAIL: You must inherit privileges role "regress_dep_user1".
REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
ERROR: permission denied to reassign objects
+DETAIL: You must inherit privileges of role "regress_dep_user1".
-- this one is allowed
DROP OWNED BY regress_dep_user0;
CREATE TABLE deptest1 (f1 int unique);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..72829ad764 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -37,7 +37,7 @@ CREATE ROLE regress_priv_role;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3;
-ERROR: admin option cannot be granted back to your own grantor
+ERROR: ADMIN option cannot be granted back to your own grantor
-- need CASCADE to revoke grant or admin option if dependent grants exist
REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail
ERROR: dependent privileges exist
@@ -156,7 +156,8 @@ ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate
NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1"
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted
-ERROR: must have CREATEROLE privilege to change another user's password
+ERROR: permission denied to alter role
+DETAIL: To change another role's password, you must have the CREATEROLE attribute and the ADMIN option on the role.
RESET SESSION AUTHORIZATION;
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1;
@@ -168,7 +169,8 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1;
-- test owner privileges
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION
-ERROR: grantor must have ADMIN OPTION on "regress_priv_role"
+ERROR: permission denied to grant privileges as role "regress_priv_role"
+DETAIL: The grantor must have the ADMIN option on role "regress_priv_role".
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE;
REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error
ERROR: role "foo" does not exist
@@ -1795,7 +1797,8 @@ REFRESH MATERIALIZED VIEW sro_mv;
ERROR: cannot fire deferred trigger within security-restricted operation
CONTEXT: SQL function "mv_action" statement 1
BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have the ADMIN option on role "regress_priv_group2".
CONTEXT: SQL function "unwanted_grant" statement 1
SQL statement "SELECT unwanted_grant()"
PL/pgSQL function sro_trojan() line 1 at PERFORM
@@ -1825,10 +1828,12 @@ CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have the ADMIN option on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have the ADMIN option on role "regress_priv_group2".
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4"
dogrant_ok
@@ -1838,10 +1843,12 @@ NOTICE: role "regress_priv_user5" has already been granted membership in role "
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have the ADMIN option on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: You must have the ADMIN option on role "regress_priv_group2".
SET SESSION AUTHORIZATION regress_priv_user4;
DROP FUNCTION dogrant_ok();
REVOKE regress_priv_group2 FROM regress_priv_user5;
--
2.25.1
v6-0002-Improve-more-insufficient-privileges-error-messag.patchtext/x-diff; charset=us-asciiDownload
From 4664631c96a303d2066380966d31df113e1fea06 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 14:52:57 -0800
Subject: [PATCH v6 2/2] Improve more insufficient-privileges error messages.
---
contrib/file_fdw/expected/file_fdw.out | 3 ++-
contrib/file_fdw/file_fdw.c | 8 ++++++--
contrib/test_decoding/expected/permissions.out | 12 ++++++++----
src/backend/backup/basebackup_server.c | 4 +++-
src/backend/catalog/objectaddress.c | 16 +++++++++++-----
src/backend/commands/copy.c | 12 +++++++++---
src/backend/replication/slot.c | 6 ++++--
src/backend/storage/ipc/procarray.c | 4 +++-
src/backend/storage/ipc/signalfuncs.c | 16 ++++++++++++----
src/backend/tcop/utility.c | 5 ++++-
src/backend/utils/init/miscinit.c | 4 ++++
src/backend/utils/init/postinit.c | 12 ++++++++----
src/backend/utils/misc/guc.c | 15 +++++++++------
.../dummy_seclabel/expected/dummy_seclabel.out | 3 ++-
.../modules/unsafe_tests/expected/rolenames.out | 3 ++-
src/test/regress/expected/create_role.out | 3 ++-
16 files changed, 89 insertions(+), 37 deletions(-)
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 36d76ba26c..ea7afd6fe8 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -474,7 +474,8 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: permission denied to set the "filename" option of a file_fdw foreign table
+DETAIL: You must inherit privileges of the "pg_read_server_files" role to set this option.
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 8ccc167548..8553d93b3f 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -278,13 +278,17 @@ file_fdw_validator(PG_FUNCTION_ARGS)
!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("permission denied to set the \"filename\" option of a file_fdw foreign table"),
+ errdetail("You must inherit privileges of the \"%s\" role to set this option.",
+ "pg_read_server_files")));
if (strcmp(def->defname, "program") == 0 &&
!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("permission denied to set the \"program\" option of a file_fdw foreign table"),
+ errdetail("You must inherit privileges of the \"%s\" role to set this option.",
+ "pg_execute_server_program")));
filename = defGetString(def);
}
diff --git a/contrib/test_decoding/expected/permissions.out b/contrib/test_decoding/expected/permissions.out
index ed97f81dda..b45f5cf0df 100644
--- a/contrib/test_decoding/expected/permissions.out
+++ b/contrib/test_decoding/expected/permissions.out
@@ -54,13 +54,16 @@ RESET ROLE;
-- plain user *can't* can control replication
SET ROLE regress_lr_normal;
SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have the REPLICATION attribute to use replication slots.
INSERT INTO lr_test VALUES('lr_superuser_init');
ERROR: permission denied for table lr_test
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have the REPLICATION attribute to use replication slots.
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have the REPLICATION attribute to use replication slots.
RESET ROLE;
-- replication users can drop superuser created slots
SET ROLE regress_lr_superuser;
@@ -90,7 +93,8 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_d
RESET ROLE;
SET ROLE regress_lr_normal;
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: You must have the REPLICATION attribute to use replication slots.
RESET ROLE;
-- all users can see existing slots
SET ROLE regress_lr_superuser;
diff --git a/src/backend/backup/basebackup_server.c b/src/backend/backup/basebackup_server.c
index 0258d7a03b..aac64408d1 100644
--- a/src/backend/backup/basebackup_server.c
+++ b/src/backend/backup/basebackup_server.c
@@ -72,7 +72,9 @@ bbsink_server_new(bbsink *next, char *pathname)
if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a role with privileges of the pg_write_server_files role to create backup stored on server")));
+ errmsg("permission denied to create backup stored on server"),
+ errdetail("You must inherit privileges of the \"%s\" role.",
+ "pg_write_server_files")));
CommitTransactionCommand();
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 2f688166e1..c1ab4ea161 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2547,20 +2547,26 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
if (!superuser_arg(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser")));
+ errmsg("permission denied"),
+ errdetail("You must have the %s attribute.",
+ "SUPERUSER")));
}
else
{
if (!has_createrole_privilege(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege")));
+ errmsg("permission denied"),
+ errdetail("You must have the %s attribute.",
+ "CREATEROLE")));
if (!is_admin_of_role(roleid, address.objectId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(address.objectId,
- true))));
+ errmsg("permission denied"),
+ errdetail("You must have the %s option on role \"%s\".",
+ "ADMIN",
+ GetUserNameFromId(address.objectId,
+ true))));
}
break;
case OBJECT_TSPARSER:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e34f583ea7..388cfd7cae 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -83,7 +83,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("permission denied to COPY to or from an external program"),
+ errdetail("You must inherit privileges of the \"%s\" role.",
+ "pg_execute_server_program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
@@ -92,14 +94,18 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
+ errmsg("permission denied to COPY from a file"),
+ errdetail("You must inherit privileges of the \"%s\" role.",
+ "pg_read_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
+ errmsg("permission denied to COPY to a file"),
+ errdetail("You must inherit privileges of the \"%s\" role.",
+ "pg_write_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index f286918f69..51adcd8418 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1140,10 +1140,12 @@ CheckSlotRequirements(void)
void
CheckSlotPermissions(void)
{
- if (!superuser() && !has_rolreplication(GetUserId()))
+ if (!has_rolreplication(GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to use replication slots")));
+ errmsg("permission denied to use replication slots"),
+ errdetail("You must have the %s attribute to use replication slots.",
+ "REPLICATION")));
}
/*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a7071b2fce..09a69f2c76 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3860,7 +3860,9 @@ TerminateOtherDBBackends(Oid databaseId)
!has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must inherit privileges of the role whose process is being terminated or inherit privileges of the \"%s\" role.",
+ "pg_signal_backend")));
}
}
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index bc93ab5b52..c1be67e7d0 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -121,12 +121,16 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to cancel superuser query")));
+ errmsg("permission denied to cancel query"),
+ errdetail("You must have the %s attribute to cancel queries of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend")));
+ errmsg("permission denied to cancel query"),
+ errdetail("You must inherit privileges of the role whose query is being canceled or inherit privileges of the \"%s\" role.",
+ "pg_signal_backend")));
PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
}
@@ -223,12 +227,16 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to terminate superuser process")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must have the %s attribute to terminate processes of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("You must inherit privileges of the role whose process is being terminated or inherit privileges of the \"%s\" role.",
+ "pg_signal_backend")));
/* Wait only on success and if actually requested */
if (r == SIGNAL_BACKEND_SUCCESS && timeout > 0)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c7d9d96b45..3fd9cfb842 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -950,7 +950,10 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_checkpoint to do CHECKPOINT")));
+ errmsg("permission denied to execute %s command",
+ "CHECKPOINT"),
+ errdetail("You must inherit privileges of the \"%s\" role to execute this command.",
+ "pg_checkpoint")));
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 59532bbd80..b0cfbc742b 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -701,6 +701,10 @@ has_rolreplication(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))
{
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 2f07ca7a0e..2b103586ab 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -940,12 +940,14 @@ InitPostgres(const char *in_dbname, Oid dboid,
if (nfree < SuperuserReservedConnections)
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for superusers")));
+ errmsg("remaining connection slots are reserved for roles with %s",
+ "SUPERUSER")));
if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_connections")));
+ errmsg("remaining connection slots are reserved for roles that inherit privileges of the \"%s\" role",
+ "pg_use_reserved_connections")));
}
/* Check replication permissions needed for walsender processes. */
@@ -953,10 +955,12 @@ InitPostgres(const char *in_dbname, Oid dboid,
{
Assert(!bootstrap);
- if (!superuser() && !has_rolreplication(GetUserId()))
+ if (!has_rolreplication(GetUserId()))
ereport(FATAL,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to start walsender")));
+ errmsg("permission denied to start WAL sender"),
+ errdetail("You must have the %s attribute to start a WAL sender process.",
+ "REPLICATION")));
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 51e07d5582..7805a6015b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4209,8 +4209,9 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must inherit privileges of the \"%s\" role to examine this parameter.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -4255,8 +4256,9 @@ GetConfigOptionResetString(const char *name)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must inherit privileges of the \"%s\" role to examine this parameter.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -5261,8 +5263,9 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("You must inherit privileges of the \"%s\" role to examine this parameter.",
+ "pg_read_all_settings")));
if (varname)
*varname = record->name;
diff --git a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
index c57d4fd2df..18f8db0e39 100644
--- a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
+++ b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
@@ -59,7 +59,8 @@ SECURITY LABEL ON ROLE regress_dummy_seclabel_user4 IS 'unclassified'; -- fail (
ERROR: role "regress_dummy_seclabel_user4" does not exist
SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
SECURITY LABEL ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- fail (not privileged)
-ERROR: must have CREATEROLE privilege
+ERROR: permission denied
+DETAIL: You must have the CREATEROLE attribute.
RESET SESSION AUTHORIZATION;
--
-- Test for various types of object
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index 88b1ff843b..6e55850705 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,8 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: permission denied to examine "session_preload_libraries"
+DETAIL: You must inherit privileges of the "pg_read_all_settings" role to examine this parameter.
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 7157d4c589..e72eb91624 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -106,7 +106,8 @@ ALTER ROLE regress_hasprivs RENAME TO regress_tenant;
ALTER ROLE regress_tenant NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-- fail, we should be unable to modify a role we did not create
COMMENT ON ROLE regress_role_normal IS 'some comment';
-ERROR: must have admin option on role "regress_role_normal"
+ERROR: permission denied
+DETAIL: You must have the ADMIN option on role "regress_role_normal".
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
DETAIL: You must have the CREATEROLE attribute and the ADMIN option on role "regress_role_normal".
--
2.25.1
On 20.02.23 23:58, Nathan Bossart wrote:
Similarly -- this is an existing issue but we might as well look at it -- in
something likemust be superuser or a role with privileges of the
pg_write_server_files rolethe phrase "a role with the privileges of that other role" seems ambiguous.
Doesn't it really mean you must be a member of that role?Membership alone is not sufficient. You must also inherit the privileges
of the role via the INHERIT option. I thought about making this something
likemust have the INHERIT option on role %s
but I'm not sure that's accurate either. That wording makes it sound lіke
you need to be granted membership to the role directly WITH INHERIT OPTION,
but what you really need is membership, direct or indirect, with an INHERIT
chain up to the role in question. However, it looks like "must have the
ADMIN option on role %s" is used to mean something similar, so perhaps I am
overthinking it.For now, I've reworded these as "must inherit privileges of".
I don't have a good mental model of all this role inheritance,
personally, but I fear that this change makes the messages more jargony
and less clear. Maybe the original wording was good enough.
A couple of other thoughts:
"admin option" is sort of a natural language term, I think, so we don't
need to parametrize it as "%s option". Also, there are no other
"options" in this context, I think.
A general thought: It seems we currently don't have any error messages
that address the user like "You must do this". Do we want to go there?
Should we try for a more impersonal wording like
"You must have the %s attribute to create roles."
"Current user must have the %s attribute to create roles."
"%s attribute is required to create roles."
By the way, I'm not sure what the separation between 0001 and 0002 is
supposed to be.
On Thu, Mar 09, 2023 at 10:55:54AM +0100, Peter Eisentraut wrote:
On 20.02.23 23:58, Nathan Bossart wrote:
For now, I've reworded these as "must inherit privileges of".
I don't have a good mental model of all this role inheritance, personally,
but I fear that this change makes the messages more jargony and less clear.
Maybe the original wording was good enough.
I'm fine with that.
"admin option" is sort of a natural language term, I think, so we don't need
to parametrize it as "%s option". Also, there are no other "options" in
this context, I think.
v16 introduces the INHERIT and SET options. I don't have a strong opinion
about parameterizing it, though. My intent was to consistently capitalize
all the attributes and options.
A general thought: It seems we currently don't have any error messages that
address the user like "You must do this". Do we want to go there? Should we
try for a more impersonal wording like"You must have the %s attribute to create roles."
"Current user must have the %s attribute to create roles."
"%s attribute is required to create roles."
I think I like the last option the most. In general, I agree with trying
to avoid the second-person phrasing.
By the way, I'm not sure what the separation between 0001 and 0002 is
supposed to be.
I'll combine them. I first started with user.c only, but we kept finding
new messages to improve.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, Mar 09, 2023 at 09:58:46AM -0800, Nathan Bossart wrote:
On Thu, Mar 09, 2023 at 10:55:54AM +0100, Peter Eisentraut wrote:
On 20.02.23 23:58, Nathan Bossart wrote:
For now, I've reworded these as "must inherit privileges of".
I don't have a good mental model of all this role inheritance, personally,
but I fear that this change makes the messages more jargony and less clear.
Maybe the original wording was good enough.I'm fine with that.
I used the original wording in v7.
"admin option" is sort of a natural language term, I think, so we don't need
to parametrize it as "%s option". Also, there are no other "options" in
this context, I think.v16 introduces the INHERIT and SET options. I don't have a strong opinion
about parameterizing it, though. My intent was to consistently capitalize
all the attributes and options.
I didn't change this in v7, but I can do so if you still think it shouldn't
be parameterized.
A general thought: It seems we currently don't have any error messages that
address the user like "You must do this". Do we want to go there? Should we
try for a more impersonal wording like"You must have the %s attribute to create roles."
"Current user must have the %s attribute to create roles."
"%s attribute is required to create roles."
I think I like the last option the most. In general, I agree with trying
to avoid the second-person phrasing.
I ended up using the "current user must have" wording in a few places, and
for most others, I used "only roles with X may do Y." That seemed to flow
relatively well, and IMO it made the required privileges abundantly clear.
I initially was going to use the "X attribute is required to Y" wording,
but I was worried that didn't make it sufficiently clear that the _role_
must have the attribute. In any case, I'm not wedded to the approach I
used in the patch and am willing to try out other wordings.
BTW I did find one example of a "you must" message while I was updating the
patch:
write_stderr("%s does not know where to find the server configuration file.\n"
"You must specify the --config-file or -D invocation "
"option or set the PGDATA environment variable.\n",
progname);
I don't think it's a common style, though.
By the way, I'm not sure what the separation between 0001 and 0002 is
supposed to be.I'll combine them. I first started with user.c only, but we kept finding
new messages to improve.
I combined the patches in v7.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v7-0001-Improve-several-permission-related-error-messages.patchtext/x-diff; charset=us-asciiDownload
From 66cc7a3999cae48c1b97ef067bc16872b31b887f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 26 Jan 2023 11:05:13 -0800
Subject: [PATCH v7 1/1] Improve several permission-related error messages.
---
contrib/file_fdw/expected/file_fdw.out | 3 +-
contrib/file_fdw/file_fdw.c | 8 +-
.../test_decoding/expected/permissions.out | 12 +-
src/backend/backup/basebackup_server.c | 4 +-
src/backend/catalog/objectaddress.c | 16 +-
src/backend/commands/copy.c | 12 +-
src/backend/commands/user.c | 169 +++++++++++++-----
src/backend/replication/slot.c | 6 +-
src/backend/storage/ipc/procarray.c | 4 +-
src/backend/storage/ipc/signalfuncs.c | 16 +-
src/backend/tcop/utility.c | 5 +-
src/backend/utils/init/miscinit.c | 4 +
src/backend/utils/init/postinit.c | 12 +-
src/backend/utils/misc/guc.c | 15 +-
.../expected/dummy_seclabel.out | 3 +-
.../unsafe_tests/expected/rolenames.out | 3 +-
src/test/regress/expected/create_role.out | 80 ++++++---
src/test/regress/expected/dependency.out | 4 +
src/test/regress/expected/privileges.out | 23 ++-
19 files changed, 287 insertions(+), 112 deletions(-)
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 36d76ba26c..a5acf6d4f7 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -474,7 +474,8 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: permission denied to set the "filename" option of a file_fdw foreign table
+DETAIL: Only roles with privileges of the "pg_read_server_files" role may set this option.
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2d2b0b6a6b..8a312b5d0e 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -278,13 +278,17 @@ file_fdw_validator(PG_FUNCTION_ARGS)
!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("permission denied to set the \"filename\" option of a file_fdw foreign table"),
+ errdetail("Only roles with privileges of the \"%s\" role may set this option.",
+ "pg_read_server_files")));
if (strcmp(def->defname, "program") == 0 &&
!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("permission denied to set the \"program\" option of a file_fdw foreign table"),
+ errdetail("Only roles with privileges of the \"%s\" role may set this option.",
+ "pg_execute_server_program")));
filename = defGetString(def);
}
diff --git a/contrib/test_decoding/expected/permissions.out b/contrib/test_decoding/expected/permissions.out
index ed97f81dda..d6eaba8c55 100644
--- a/contrib/test_decoding/expected/permissions.out
+++ b/contrib/test_decoding/expected/permissions.out
@@ -54,13 +54,16 @@ RESET ROLE;
-- plain user *can't* can control replication
SET ROLE regress_lr_normal;
SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: Only roles with the REPLICATION attribute may use replication slots.
INSERT INTO lr_test VALUES('lr_superuser_init');
ERROR: permission denied for table lr_test
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: Only roles with the REPLICATION attribute may use replication slots.
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: Only roles with the REPLICATION attribute may use replication slots.
RESET ROLE;
-- replication users can drop superuser created slots
SET ROLE regress_lr_superuser;
@@ -90,7 +93,8 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_d
RESET ROLE;
SET ROLE regress_lr_normal;
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: Only roles with the REPLICATION attribute may use replication slots.
RESET ROLE;
-- all users can see existing slots
SET ROLE regress_lr_superuser;
diff --git a/src/backend/backup/basebackup_server.c b/src/backend/backup/basebackup_server.c
index 0258d7a03b..2b9d9d2932 100644
--- a/src/backend/backup/basebackup_server.c
+++ b/src/backend/backup/basebackup_server.c
@@ -72,7 +72,9 @@ bbsink_server_new(bbsink *next, char *pathname)
if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a role with privileges of the pg_write_server_files role to create backup stored on server")));
+ errmsg("permission denied to create backup stored on server"),
+ errdetail("Only roles with privileges of the \"%s\" role may create a backup stored on the server.",
+ "pg_write_server_files")));
CommitTransactionCommand();
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 2f688166e1..d59492934c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2547,20 +2547,26 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
if (!superuser_arg(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser")));
+ errmsg("permission denied"),
+ errdetail("The current user must have the %s attribute.",
+ "SUPERUSER")));
}
else
{
if (!has_createrole_privilege(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege")));
+ errmsg("permission denied"),
+ errdetail("The current user must have the %s attribute.",
+ "CREATEROLE")));
if (!is_admin_of_role(roleid, address.objectId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(address.objectId,
- true))));
+ errmsg("permission denied"),
+ errdetail("The current user must have the %s option on role \"%s\".",
+ "ADMIN",
+ GetUserNameFromId(address.objectId,
+ true))));
}
break;
case OBJECT_TSPARSER:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e34f583ea7..5fee0d963b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -83,7 +83,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("permission denied to COPY to or from an external program"),
+ errdetail("Only roles with privileges of the \"%s\" role may COPY to or from an external program.",
+ "pg_execute_server_program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
@@ -92,14 +94,18 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
+ errmsg("permission denied to COPY from a file"),
+ errdetail("Only roles with privileges of the \"%s\" role may COPY from a file.",
+ "pg_read_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
+ errmsg("permission denied to COPY to a file"),
+ errdetail("Only roles with privileges of the \"%s\" role may COPY to a file.",
+ "pg_write_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 3a92e930c0..2411f9ab9d 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -316,23 +316,33 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles.",
+ "CREATEROLE")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create superusers")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb permission to create createdb users")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles with %s.",
+ "CREATEDB", "CREATEDB")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication permission to create replication users")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles with %s.",
+ "REPLICATION", "REPLICATION")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls to create bypassrls users")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles with %s.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/*
@@ -744,10 +754,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
roleid = authform->oid;
/* To mess with a superuser in any way you gotta be superuser. */
- if (!superuser() && (authform->rolsuper || dissuper))
+ if (!superuser() && authform->rolsuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ if (!superuser() && dissuper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may change the %s attribute.",
+ "SUPERUSER", "SUPERUSER")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -761,13 +779,17 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
dvalidUntil || disreplication || dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may alter this role.",
+ "CREATEROLE", "ADMIN", rolename)));
/* an unprivileged user can change their own password */
if (dpassword && roleid != currentUserId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errdetail("To change another role's password, the current user must have the %s attribute and the %s option on the role.",
+ "CREATEROLE", "ADMIN")));
}
else if (!superuser())
{
@@ -779,23 +801,30 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb privilege to change createdb attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may change the %s attribute.",
+ "CREATEDB", "CREATEDB")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication privilege to change replication attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may change the %s attribute.",
+ "REPLICATION", "REPLICATION")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may change the %s attribute.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\" to add members",
- rolename)));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s option on role \"%s\" may add members.",
+ "ADMIN", rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
@@ -837,8 +866,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: bootstrap user must be superuser")));
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("permission denied to alter role"),
+ errdetail("The bootstrap user must have the %s attribute.",
+ "SUPERUSER")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
@@ -999,7 +1030,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1008,7 +1041,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may alter this role.",
+ "CREATROLE", "ADMIN", NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
@@ -1038,7 +1073,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter settings globally")));
+ errmsg("permission denied to alter setting"),
+ errdetail("Only roles with the %s attribute may alter settings globally.",
+ "SUPERUSER")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1061,7 +1098,9 @@ DropRole(DropRoleStmt *stmt)
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop role")));
+ errmsg("permission denied to drop role"),
+ errdetail("Only roles with the %s attribute and the %s option on the target roles may drop roles.",
+ "CREATEROLE", "ADMIN")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
@@ -1131,12 +1170,15 @@ DropRole(DropRoleStmt *stmt)
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to drop superusers")));
+ errmsg("permission denied to drop role"),
+ errdetail("Only roles with the %s attribute may drop roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may drop this role.",
+ "CREATEROLE", "ADMIN", NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
@@ -1378,12 +1420,14 @@ RenameRole(const char *oldname, const char *newname)
* Only superusers can mess with superusers. Otherwise, a user with
* CREATEROLE can rename a role for which they have ADMIN OPTION.
*/
- if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
+ if (authform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to rename superusers")));
+ errmsg("permission denied to rename role"),
+ errdetail("Only roles with the %s attribute may rename roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1391,7 +1435,9 @@ RenameRole(const char *oldname, const char *newname)
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to rename role")));
+ errmsg("permission denied to rename role"),
+ errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may rename this role.",
+ "CREATEROLE", "ADMIN", NameStr(authform->rolname))));
}
/* OK, construct the modified tuple */
@@ -1554,7 +1600,9 @@ DropOwnedObjects(DropOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop objects")));
+ errmsg("permission denied to drop objects"),
+ errdetail("Only roles with privileges of role \"%s\" may drop its objects.",
+ GetUserNameFromId(roleid, false))));
}
/* Ok, do it */
@@ -1581,7 +1629,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("Only roles with privileges of role \"%s\" may reassign its objects.",
+ GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
@@ -1590,7 +1640,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("Only roles with privileges of role \"%s\" may reassign objects to it.",
+ GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
@@ -1738,7 +1790,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s option cannot be granted back to your own grantor",
+ "ADMIN")));
plan_member_revoke(memlist, actions, memberid);
}
@@ -1763,7 +1816,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s option cannot be granted back to your own grantor",
+ "ADMIN")));
ReleaseSysCacheList(memlist);
}
@@ -2081,9 +2135,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("Only roles with the %s attribute may grant roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("Only roles with the %s attribute may revoke roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ }
}
else
{
@@ -2091,10 +2158,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(roleid, false))));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("Only roles with the %s option on role \"%s\" may grant this role.",
+ "ADMIN", GetUserNameFromId(roleid, false))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("Only roles with the %s option on role \"%s\" may revoke this role.",
+ "ADMIN", GetUserNameFromId(roleid, false))));
+ }
}
}
@@ -2173,14 +2252,18 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("Only roles with privileges of role \"%s\" may grant privileges as this role.",
+ GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("grantor must have ADMIN OPTION on \"%s\"",
- GetUserNameFromId(roleid, false))));
+ errmsg("permission denied to grant privileges as role \"%s\"",
+ GetUserNameFromId(grantorId, false)),
+ errdetail("The grantor must have the %s option on role \"%s\".",
+ "ADMIN", GetUserNameFromId(roleid, false))));
}
else
{
@@ -2188,7 +2271,9 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("Only roles with privileges of role \"%s\" may revoke privileges granted by this role.",
+ GetUserNameFromId(grantorId, false))));
}
/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index f286918f69..2293c0c6fc 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1140,10 +1140,12 @@ CheckSlotRequirements(void)
void
CheckSlotPermissions(void)
{
- if (!superuser() && !has_rolreplication(GetUserId()))
+ if (!has_rolreplication(GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to use replication slots")));
+ errmsg("permission denied to use replication slots"),
+ errdetail("Only roles with the %s attribute may use replication slots.",
+ "REPLICATION")));
}
/*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a9fb97460d..ea91ce355f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3883,7 +3883,9 @@ TerminateOtherDBBackends(Oid databaseId)
!has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("Only roles with privileges of the role whose process is being terminated or with privileges of the \"%s\" role may terminate this process.",
+ "pg_signal_backend")));
}
}
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index bc93ab5b52..eabb68a9e1 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -121,12 +121,16 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to cancel superuser query")));
+ errmsg("permission denied to cancel query"),
+ errdetail("Only roles with the %s attribute may cancel queries of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend")));
+ errmsg("permission denied to cancel query"),
+ errdetail("Only roles with privileges of the role whose query is being canceled or with privileges of the \"%s\" role may cancel this query.",
+ "pg_signal_backend")));
PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
}
@@ -223,12 +227,16 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to terminate superuser process")));
+ errmsg("permission denied to terminate process"),
+ errdetail("Only roles with the %s attribute may terminate processes of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("Only roles with privileges of the role whose process is being terminated or with privileges of the \"%s\" role may terminate this process.",
+ "pg_signal_backend")));
/* Wait only on success and if actually requested */
if (r == SIGNAL_BACKEND_SUCCESS && timeout > 0)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c7d9d96b45..eada735363 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -950,7 +950,10 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_checkpoint to do CHECKPOINT")));
+ errmsg("permission denied to execute %s command",
+ "CHECKPOINT"),
+ errdetail("Only roles with privileges of the \"%s\" role may execute this command.",
+ "pg_checkpoint")));
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 7eb7fe87f6..a604432126 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -709,6 +709,10 @@ has_rolreplication(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))
{
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b0e20cc635..92bac8b63f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -949,12 +949,14 @@ InitPostgres(const char *in_dbname, Oid dboid,
if (nfree < SuperuserReservedConnections)
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for superusers")));
+ errmsg("remaining connection slots are reserved for roles with %s",
+ "SUPERUSER")));
if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_connections")));
+ errmsg("remaining connection slots are reserved for roles with privileges of the \"%s\" role",
+ "pg_use_reserved_connections")));
}
/* Check replication permissions needed for walsender processes. */
@@ -962,10 +964,12 @@ InitPostgres(const char *in_dbname, Oid dboid,
{
Assert(!bootstrap);
- if (!superuser() && !has_rolreplication(GetUserId()))
+ if (!has_rolreplication(GetUserId()))
ereport(FATAL,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to start walsender")));
+ errmsg("permission denied to start WAL sender"),
+ errdetail("Only roles with the %s attribute may start a WAL sender process.",
+ "REPLICATION")));
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 51e07d5582..ea67cfa5e5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4209,8 +4209,9 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -4255,8 +4256,9 @@ GetConfigOptionResetString(const char *name)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -5261,8 +5263,9 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.",
+ "pg_read_all_settings")));
if (varname)
*varname = record->name;
diff --git a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
index c57d4fd2df..de671e5a17 100644
--- a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
+++ b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
@@ -59,7 +59,8 @@ SECURITY LABEL ON ROLE regress_dummy_seclabel_user4 IS 'unclassified'; -- fail (
ERROR: role "regress_dummy_seclabel_user4" does not exist
SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
SECURITY LABEL ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- fail (not privileged)
-ERROR: must have CREATEROLE privilege
+ERROR: permission denied
+DETAIL: The current user must have the CREATEROLE attribute.
RESET SESSION AUTHORIZATION;
--
-- Test for various types of object
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index 88b1ff843b..61396b2a80 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,8 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: permission denied to examine "session_preload_libraries"
+DETAIL: Only roles with privileges of the "pg_read_all_settings" role may examine this parameter.
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 9f431bd4f5..98451287ab 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -7,26 +7,35 @@ CREATE ROLE regress_role_normal;
-- fail, CREATEROLE user can't give away role attributes without having them
SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
-ERROR: must be superuser to create superusers
+ERROR: permission denied to create role
+DETAIL: Only roles with the SUPERUSER attribute may create roles with SUPERUSER.
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: Only roles with the REPLICATION attribute may create roles with REPLICATION.
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: Only roles with the REPLICATION attribute may create roles with REPLICATION.
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must have bypassrls to create bypassrls users
+ERROR: permission denied to create role
+DETAIL: Only roles with the BYPASSRLS attribute may create roles with BYPASSRLS.
CREATE ROLE regress_nosuch_createdb CREATEDB;
-ERROR: must have createdb permission to create createdb users
+ERROR: permission denied to create role
+DETAIL: Only roles with the CREATEDB attribute may create roles with CREATEDB.
-- ok, can create a role without any special attributes
CREATE ROLE regress_role_limited;
-- fail, can't give it in any of the restricted attributes
ALTER ROLE regress_role_limited SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the SUPERUSER attribute may change the SUPERUSER attribute.
ALTER ROLE regress_role_limited REPLICATION;
-ERROR: must have replication privilege to change replication attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the REPLICATION attribute may change the REPLICATION attribute.
ALTER ROLE regress_role_limited CREATEDB;
-ERROR: must have createdb privilege to change createdb attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the CREATEDB attribute may change the CREATEDB attribute.
ALTER ROLE regress_role_limited BYPASSRLS;
-ERROR: must have bypassrls privilege to change bypassrls attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the BYPASSRLS attribute may change the BYPASSRLS attribute.
DROP ROLE regress_role_limited;
-- ok, can give away these role attributes if you have them
SET SESSION AUTHORIZATION regress_role_admin;
@@ -43,9 +52,11 @@ ALTER ROLE regress_createdb NOCREATEDB;
ALTER ROLE regress_createdb CREATEDB;
-- fail, can't toggle SUPERUSER
ALTER ROLE regress_createdb SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the SUPERUSER attribute may change the SUPERUSER attribute.
ALTER ROLE regress_createdb NOSUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the SUPERUSER attribute may change the SUPERUSER attribute.
-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
@@ -59,7 +70,8 @@ CREATE ROLE regress_noiseword SYSID 12345;
NOTICE: SYSID can no longer be specified
-- fail, cannot grant membership in superuser role
CREATE ROLE regress_nosuch_super IN ROLE regress_role_super;
-ERROR: must be superuser to alter superusers
+ERROR: permission denied to grant role "regress_role_super"
+DETAIL: Only roles with the SUPERUSER attribute may grant roles with SUPERUSER.
-- fail, database owner cannot have members
CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner;
ERROR: role "pg_database_owner" cannot have explicit members
@@ -94,11 +106,14 @@ ALTER ROLE regress_hasprivs RENAME TO regress_tenant;
ALTER ROLE regress_tenant NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-- fail, we should be unable to modify a role we did not create
COMMENT ON ROLE regress_role_normal IS 'some comment';
-ERROR: must have admin option on role "regress_role_normal"
+ERROR: permission denied
+DETAIL: The current user must have the ADMIN option on role "regress_role_normal".
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
+DETAIL: Only roles with the CREATEROLE attribute and the ADMIN option on role "regress_role_normal" may rename this role.
ALTER ROLE regress_role_normal NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-ERROR: permission denied
+ERROR: permission denied to alter role
+DETAIL: Only roles with the CREATEROLE attribute and the ADMIN option on role "regress_role_normal" may alter this role.
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
@@ -123,6 +138,7 @@ ERROR: must be able to SET ROLE "regress_tenant"
-- fail, we don't inherit permissions from regress_tenant
REASSIGN OWNED BY regress_tenant TO regress_createrole;
ERROR: permission denied to reassign objects
+DETAIL: Only roles with privileges of role "regress_tenant" may reassign its objects.
-- ok, create a role with a value for createrole_self_grant
SET createrole_self_grant = 'set, inherit';
CREATE ROLE regress_tenant2;
@@ -150,25 +166,35 @@ ERROR: must be able to SET ROLE "regress_tenant2"
DROP TABLE tenant2_table;
-- fail, CREATEROLE is not enough to create roles in privileged roles
CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data;
-ERROR: must have admin option on role "pg_read_all_data"
+ERROR: permission denied to grant role "pg_read_all_data"
+DETAIL: Only roles with the ADMIN option on role "pg_read_all_data" may grant this role.
CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data;
-ERROR: must have admin option on role "pg_write_all_data"
+ERROR: permission denied to grant role "pg_write_all_data"
+DETAIL: Only roles with the ADMIN option on role "pg_write_all_data" may grant this role.
CREATE ROLE regress_monitor IN ROLE pg_monitor;
-ERROR: must have admin option on role "pg_monitor"
+ERROR: permission denied to grant role "pg_monitor"
+DETAIL: Only roles with the ADMIN option on role "pg_monitor" may grant this role.
CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings;
-ERROR: must have admin option on role "pg_read_all_settings"
+ERROR: permission denied to grant role "pg_read_all_settings"
+DETAIL: Only roles with the ADMIN option on role "pg_read_all_settings" may grant this role.
CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats;
-ERROR: must have admin option on role "pg_read_all_stats"
+ERROR: permission denied to grant role "pg_read_all_stats"
+DETAIL: Only roles with the ADMIN option on role "pg_read_all_stats" may grant this role.
CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables;
-ERROR: must have admin option on role "pg_stat_scan_tables"
+ERROR: permission denied to grant role "pg_stat_scan_tables"
+DETAIL: Only roles with the ADMIN option on role "pg_stat_scan_tables" may grant this role.
CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files;
-ERROR: must have admin option on role "pg_read_server_files"
+ERROR: permission denied to grant role "pg_read_server_files"
+DETAIL: Only roles with the ADMIN option on role "pg_read_server_files" may grant this role.
CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files;
-ERROR: must have admin option on role "pg_write_server_files"
+ERROR: permission denied to grant role "pg_write_server_files"
+DETAIL: Only roles with the ADMIN option on role "pg_write_server_files" may grant this role.
CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program;
-ERROR: must have admin option on role "pg_execute_server_program"
+ERROR: permission denied to grant role "pg_execute_server_program"
+DETAIL: Only roles with the ADMIN option on role "pg_execute_server_program" may grant this role.
CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend;
-ERROR: must have admin option on role "pg_signal_backend"
+ERROR: permission denied to grant role "pg_signal_backend"
+DETAIL: Only roles with the ADMIN option on role "pg_signal_backend" may grant this role.
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
@@ -211,11 +237,13 @@ DROP ROLE regress_inroles;
DROP ROLE regress_adminroles;
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
DROP ROLE regress_role_super;
-ERROR: must be superuser to drop superusers
+ERROR: permission denied to drop role
+DETAIL: Only roles with the SUPERUSER attribute may drop roles with SUPERUSER.
DROP ROLE regress_role_admin;
ERROR: current user cannot be dropped
DROP ROLE regress_rolecreator;
-ERROR: must have admin option on role "regress_rolecreator"
+ERROR: permission denied to drop role
+DETAIL: Only roles with the CREATEROLE attribute and the ADMIN option on role "regress_rolecreator" may drop this role.
-- ok
RESET SESSION AUTHORIZATION;
REVOKE CREATE ON DATABASE regression FROM regress_role_admin CASCADE;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 520035f6a0..4b9a6d044e 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -48,12 +48,16 @@ SET SESSION AUTHORIZATION regress_dep_user0;
-- permission denied
DROP OWNED BY regress_dep_user1;
ERROR: permission denied to drop objects
+DETAIL: Only roles with privileges of role "regress_dep_user1" may drop its objects.
DROP OWNED BY regress_dep_user0, regress_dep_user2;
ERROR: permission denied to drop objects
+DETAIL: Only roles with privileges of role "regress_dep_user2" may drop its objects.
REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
ERROR: permission denied to reassign objects
+DETAIL: Only roles with privileges of role "regress_dep_user1" may reassign objects to it.
REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
ERROR: permission denied to reassign objects
+DETAIL: Only roles with privileges of role "regress_dep_user1" may reassign its objects.
-- this one is allowed
DROP OWNED BY regress_dep_user0;
CREATE TABLE deptest1 (f1 int unique);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 5496ec8f55..3cf4ac8c9e 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -37,7 +37,7 @@ CREATE ROLE regress_priv_role;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3;
-ERROR: admin option cannot be granted back to your own grantor
+ERROR: ADMIN option cannot be granted back to your own grantor
-- need CASCADE to revoke grant or admin option if dependent grants exist
REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail
ERROR: dependent privileges exist
@@ -156,7 +156,8 @@ ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate
NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1"
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted
-ERROR: must have CREATEROLE privilege to change another user's password
+ERROR: permission denied to alter role
+DETAIL: To change another role's password, the current user must have the CREATEROLE attribute and the ADMIN option on the role.
RESET SESSION AUTHORIZATION;
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1;
@@ -168,7 +169,8 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1;
-- test owner privileges
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION
-ERROR: grantor must have ADMIN OPTION on "regress_priv_role"
+ERROR: permission denied to grant privileges as role "regress_priv_role"
+DETAIL: The grantor must have the ADMIN option on role "regress_priv_role".
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE;
REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error
ERROR: role "foo" does not exist
@@ -1795,7 +1797,8 @@ REFRESH MATERIALIZED VIEW sro_mv;
ERROR: cannot fire deferred trigger within security-restricted operation
CONTEXT: SQL function "mv_action" statement 1
BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
CONTEXT: SQL function "unwanted_grant" statement 1
SQL statement "SELECT unwanted_grant()"
PL/pgSQL function sro_trojan() line 1 at PERFORM
@@ -1825,10 +1828,12 @@ CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4"
dogrant_ok
@@ -1838,10 +1843,12 @@ NOTICE: role "regress_priv_user5" has already been granted membership in role "
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
SET SESSION AUTHORIZATION regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
SET SESSION AUTHORIZATION regress_priv_user4;
DROP FUNCTION dogrant_ok();
REVOKE regress_priv_group2 FROM regress_priv_user5;
--
2.25.1
On 10.03.23 01:03, Nathan Bossart wrote:
By the way, I'm not sure what the separation between 0001 and 0002 is
supposed to be.I'll combine them. I first started with user.c only, but we kept finding
new messages to improve.I combined the patches in v7.
I have committed two pieces that were not message changes separately.
I think the following change in DropRole() is incorrect:
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errdetail("Only roles with the %s attribute and the
%s option on role \"%s\" may drop this role.",
+ "CREATEROLE", "ADMIN",
NameStr(roleform->rolname))));
The message does not reflect what check is actually performed. (Perhaps
this was confused with a similar but not exactly the same check in
RenameRole().)
That was the only "factual" error that I found.
In file_fdw_validator(), the option names "filename" and "program" could
be parameterized.
In DropOwnedObjects() and ReassignOwnedObjects(), I suggest the
following changes, for clarity:
- errdetail("Only roles with privileges of role \"%s\" may drop its
objects.",
+ errdetail("Only roles with privileges of role \"%s\" may drop objects
owned by it.",
- errdetail("Only roles with privileges of role \"%s\" may reassign its
objects.",
+ errdetail("Only roles with privileges of role \"%s\" may reassign
objects owned by it.",
The rest looks okay to me.
On Thu, Mar 16, 2023 at 04:24:07PM +0100, Peter Eisentraut wrote:
I have committed two pieces that were not message changes separately.
Thanks!
I think the following change in DropRole() is incorrect:
if (!is_admin_of_role(GetUserId(), roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must have admin option on role \"%s\"", - role))); + errmsg("permission denied to drop role"), + errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may drop this role.", + "CREATEROLE", "ADMIN", NameStr(roleform->rolname))));The message does not reflect what check is actually performed. (Perhaps
this was confused with a similar but not exactly the same check in
RenameRole().)
Hm. Is your point that we should only mention the admin option here? I
mentioned both createrole and admin option in this message (and the
createrole check above this point) in an attempt to avoid giving partial
information.
In file_fdw_validator(), the option names "filename" and "program" could be
parameterized.In DropOwnedObjects() and ReassignOwnedObjects(), I suggest the following
changes, for clarity:- errdetail("Only roles with privileges of role \"%s\" may drop its objects.", + errdetail("Only roles with privileges of role \"%s\" may drop objects owned by it.",- errdetail("Only roles with privileges of role \"%s\" may reassign its objects.", + errdetail("Only roles with privileges of role \"%s\" may reassign objects owned by it.",
Will do.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On 16.03.23 16:48, Nathan Bossart wrote:
I think the following change in DropRole() is incorrect:
if (!is_admin_of_role(GetUserId(), roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must have admin option on role \"%s\"", - role))); + errmsg("permission denied to drop role"), + errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may drop this role.", + "CREATEROLE", "ADMIN", NameStr(roleform->rolname))));The message does not reflect what check is actually performed. (Perhaps
this was confused with a similar but not exactly the same check in
RenameRole().)Hm. Is your point that we should only mention the admin option here? I
mentioned both createrole and admin option in this message (and the
createrole check above this point) in an attempt to avoid giving partial
information.
AFAICT, the mention of CREATEROLE is incorrect, because the code doesn't
actually check for the CREATEROLE attribute.
On Thu, Mar 16, 2023 at 04:59:53PM +0100, Peter Eisentraut wrote:
On 16.03.23 16:48, Nathan Bossart wrote:
I think the following change in DropRole() is incorrect:
if (!is_admin_of_role(GetUserId(), roleid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must have admin option on role \"%s\"", - role))); + errmsg("permission denied to drop role"), + errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may drop this role.", + "CREATEROLE", "ADMIN", NameStr(roleform->rolname))));The message does not reflect what check is actually performed. (Perhaps
this was confused with a similar but not exactly the same check in
RenameRole().)Hm. Is your point that we should only mention the admin option here? I
mentioned both createrole and admin option in this message (and the
createrole check above this point) in an attempt to avoid giving partial
information.AFAICT, the mention of CREATEROLE is incorrect, because the code doesn't
actually check for the CREATEROLE attribute.
There is a createrole check at the top of DropRole():
/*
* DROP ROLE
*/
void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel,
pg_auth_members_rel;
ListCell *item;
List *role_addresses = NIL;
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to drop role")));
Granted, no one will see the admin option error unless they at least have
createrole, so we could leave it out, but my intent was to list the full
set of privileges required to drop the role to avoid ambiguity.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Here is a rebased patch in which I've addressed the latest feedback except
for the DropRole() part that is under discussion.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v8-0001-Improve-several-permission-related-error-messages.patchtext/x-diff; charset=us-asciiDownload
From cd6a75109471e173869a15b39342ff4882eac61f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 26 Jan 2023 11:05:13 -0800
Subject: [PATCH v8 1/1] Improve several permission-related error messages.
---
contrib/file_fdw/expected/file_fdw.out | 3 +-
contrib/file_fdw/file_fdw.c | 10 +-
.../test_decoding/expected/permissions.out | 12 +-
src/backend/backup/basebackup_server.c | 4 +-
src/backend/catalog/objectaddress.c | 16 +-
src/backend/commands/copy.c | 12 +-
src/backend/commands/user.c | 167 +++++++++++++-----
src/backend/replication/slot.c | 4 +-
src/backend/storage/ipc/procarray.c | 4 +-
src/backend/storage/ipc/signalfuncs.c | 16 +-
src/backend/tcop/utility.c | 5 +-
src/backend/utils/init/postinit.c | 10 +-
src/backend/utils/misc/guc.c | 15 +-
.../expected/dummy_seclabel.out | 3 +-
.../unsafe_tests/expected/rolenames.out | 3 +-
src/test/regress/expected/create_role.out | 80 ++++++---
src/test/regress/expected/dependency.out | 4 +
src/test/regress/expected/privileges.out | 23 ++-
18 files changed, 282 insertions(+), 109 deletions(-)
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index f5ae29732a..72304e0ff3 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -491,7 +491,8 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: permission denied to set the "filename" option of a file_fdw foreign table
+DETAIL: Only roles with privileges of the "pg_read_server_files" role may set this option.
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 99b21e8316..9e330b9934 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -279,13 +279,19 @@ file_fdw_validator(PG_FUNCTION_ARGS)
!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("permission denied to set the \"%s\" option of a file_fdw foreign table",
+ "filename"),
+ errdetail("Only roles with privileges of the \"%s\" role may set this option.",
+ "pg_read_server_files")));
if (strcmp(def->defname, "program") == 0 &&
!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("permission denied to set the \"%s\" option of a file_fdw foreign table",
+ "program"),
+ errdetail("Only roles with privileges of the \"%s\" role may set this option.",
+ "pg_execute_server_program")));
filename = defGetString(def);
}
diff --git a/contrib/test_decoding/expected/permissions.out b/contrib/test_decoding/expected/permissions.out
index ed97f81dda..d6eaba8c55 100644
--- a/contrib/test_decoding/expected/permissions.out
+++ b/contrib/test_decoding/expected/permissions.out
@@ -54,13 +54,16 @@ RESET ROLE;
-- plain user *can't* can control replication
SET ROLE regress_lr_normal;
SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: Only roles with the REPLICATION attribute may use replication slots.
INSERT INTO lr_test VALUES('lr_superuser_init');
ERROR: permission denied for table lr_test
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: Only roles with the REPLICATION attribute may use replication slots.
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: Only roles with the REPLICATION attribute may use replication slots.
RESET ROLE;
-- replication users can drop superuser created slots
SET ROLE regress_lr_superuser;
@@ -90,7 +93,8 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_d
RESET ROLE;
SET ROLE regress_lr_normal;
SELECT pg_drop_replication_slot('regression_slot');
-ERROR: must be superuser or replication role to use replication slots
+ERROR: permission denied to use replication slots
+DETAIL: Only roles with the REPLICATION attribute may use replication slots.
RESET ROLE;
-- all users can see existing slots
SET ROLE regress_lr_superuser;
diff --git a/src/backend/backup/basebackup_server.c b/src/backend/backup/basebackup_server.c
index 0258d7a03b..2b9d9d2932 100644
--- a/src/backend/backup/basebackup_server.c
+++ b/src/backend/backup/basebackup_server.c
@@ -72,7 +72,9 @@ bbsink_server_new(bbsink *next, char *pathname)
if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a role with privileges of the pg_write_server_files role to create backup stored on server")));
+ errmsg("permission denied to create backup stored on server"),
+ errdetail("Only roles with privileges of the \"%s\" role may create a backup stored on the server.",
+ "pg_write_server_files")));
CommitTransactionCommand();
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 2f688166e1..d59492934c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2547,20 +2547,26 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
if (!superuser_arg(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser")));
+ errmsg("permission denied"),
+ errdetail("The current user must have the %s attribute.",
+ "SUPERUSER")));
}
else
{
if (!has_createrole_privilege(roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege")));
+ errmsg("permission denied"),
+ errdetail("The current user must have the %s attribute.",
+ "CREATEROLE")));
if (!is_admin_of_role(roleid, address.objectId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(address.objectId,
- true))));
+ errmsg("permission denied"),
+ errdetail("The current user must have the %s option on role \"%s\".",
+ "ADMIN",
+ GetUserNameFromId(address.objectId,
+ true))));
}
break;
case OBJECT_TSPARSER:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 167d31a2d9..f14fae3308 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -83,7 +83,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("permission denied to COPY to or from an external program"),
+ errdetail("Only roles with privileges of the \"%s\" role may COPY to or from an external program.",
+ "pg_execute_server_program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
@@ -92,14 +94,18 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
+ errmsg("permission denied to COPY from a file"),
+ errdetail("Only roles with privileges of the \"%s\" role may COPY from a file.",
+ "pg_read_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
+ errmsg("permission denied to COPY to a file"),
+ errdetail("Only roles with privileges of the \"%s\" role may COPY to a file.",
+ "pg_write_server_files"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 43fe530a96..52796e9548 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -316,23 +316,33 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create role")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles.",
+ "CREATEROLE")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create superusers")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb permission to create createdb users")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles with %s.",
+ "CREATEDB", "CREATEDB")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication permission to create replication users")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles with %s.",
+ "REPLICATION", "REPLICATION")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls to create bypassrls users")));
+ errmsg("permission denied to create role"),
+ errdetail("Only roles with the %s attribute may create roles with %s.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/*
@@ -744,10 +754,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
roleid = authform->oid;
/* To mess with a superuser in any way you gotta be superuser. */
- if (!superuser() && (authform->rolsuper || dissuper))
+ if (!superuser() && authform->rolsuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superuser roles or change superuser attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ if (!superuser() && dissuper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may change the %s attribute.",
+ "SUPERUSER", "SUPERUSER")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
@@ -761,13 +779,17 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
dvalidUntil || disreplication || dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may alter this role.",
+ "CREATEROLE", "ADMIN", rolename)));
/* an unprivileged user can change their own password */
if (dpassword && roleid != currentUserId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have CREATEROLE privilege to change another user's password")));
+ errmsg("permission denied to alter role"),
+ errdetail("To change another role's password, the current user must have the %s attribute and the %s option on the role.",
+ "CREATEROLE", "ADMIN")));
}
else if (!superuser())
{
@@ -779,23 +801,30 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have createdb privilege to change createdb attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may change the %s attribute.",
+ "CREATEDB", "CREATEDB")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have replication privilege to change replication attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may change the %s attribute.",
+ "REPLICATION", "REPLICATION")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have bypassrls privilege to change bypassrls attribute")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may change the %s attribute.",
+ "BYPASSRLS", "BYPASSRLS")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\" to add members",
- rolename)));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s option on role \"%s\" may add members.",
+ "ADMIN", rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
@@ -837,8 +866,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: bootstrap user must be superuser")));
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("permission denied to alter role"),
+ errdetail("The bootstrap user must have the %s attribute.",
+ "SUPERUSER")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
@@ -999,7 +1030,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute may alter roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1008,7 +1041,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied")));
+ errmsg("permission denied to alter role"),
+ errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may alter this role.",
+ "CREATROLE", "ADMIN", NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
@@ -1038,7 +1073,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter settings globally")));
+ errmsg("permission denied to alter setting"),
+ errdetail("Only roles with the %s attribute may alter settings globally.",
+ "SUPERUSER")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
@@ -1061,7 +1098,9 @@ DropRole(DropRoleStmt *stmt)
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop role")));
+ errmsg("permission denied to drop role"),
+ errdetail("Only roles with the %s attribute and the %s option on the target roles may drop roles.",
+ "CREATEROLE", "ADMIN")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
@@ -1131,12 +1170,15 @@ DropRole(DropRoleStmt *stmt)
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to drop superusers")));
+ errmsg("permission denied to drop role"),
+ errdetail("Only roles with the %s attribute may drop roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- role)));
+ errmsg("permission denied to drop role"),
+ errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may drop this role.",
+ "CREATEROLE", "ADMIN", NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
@@ -1383,7 +1425,9 @@ RenameRole(const char *oldname, const char *newname)
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to rename superusers")));
+ errmsg("permission denied to rename role"),
+ errdetail("Only roles with the %s attribute may rename roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
}
else
{
@@ -1391,7 +1435,9 @@ RenameRole(const char *oldname, const char *newname)
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to rename role")));
+ errmsg("permission denied to rename role"),
+ errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may rename this role.",
+ "CREATEROLE", "ADMIN", NameStr(authform->rolname))));
}
/* OK, construct the modified tuple */
@@ -1554,7 +1600,9 @@ DropOwnedObjects(DropOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop objects")));
+ errmsg("permission denied to drop objects"),
+ errdetail("Only roles with privileges of role \"%s\" may drop objects owned by it.",
+ GetUserNameFromId(roleid, false))));
}
/* Ok, do it */
@@ -1581,7 +1629,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("Only roles with privileges of role \"%s\" may reassign objects owned by it.",
+ GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
@@ -1590,7 +1640,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to reassign objects")));
+ errmsg("permission denied to reassign objects"),
+ errdetail("Only roles with privileges of role \"%s\" may reassign objects to it.",
+ GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
@@ -1738,7 +1790,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s option cannot be granted back to your own grantor",
+ "ADMIN")));
plan_member_revoke(memlist, actions, memberid);
}
@@ -1763,7 +1816,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("admin option cannot be granted back to your own grantor")));
+ errmsg("%s option cannot be granted back to your own grantor",
+ "ADMIN")));
ReleaseSysCacheList(memlist);
}
@@ -2081,9 +2135,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter superusers")));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("Only roles with the %s attribute may grant roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("Only roles with the %s attribute may revoke roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
+ }
}
else
{
@@ -2091,10 +2158,22 @@ check_role_membership_authorization(Oid currentUserId, Oid roleid,
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must have admin option on role \"%s\"",
- GetUserNameFromId(roleid, false))));
+ {
+ if (is_grant)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to grant role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("Only roles with the %s option on role \"%s\" may grant this role.",
+ "ADMIN", GetUserNameFromId(roleid, false))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to revoke role \"%s\"",
+ GetUserNameFromId(roleid, false)),
+ errdetail("Only roles with the %s option on role \"%s\" may revoke this role.",
+ "ADMIN", GetUserNameFromId(roleid, false))));
+ }
}
}
@@ -2173,14 +2252,18 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("Only roles with privileges of role \"%s\" may grant privileges as this role.",
+ GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("grantor must have ADMIN OPTION on \"%s\"",
- GetUserNameFromId(roleid, false))));
+ errmsg("permission denied to grant privileges as role \"%s\"",
+ GetUserNameFromId(grantorId, false)),
+ errdetail("The grantor must have the %s option on role \"%s\".",
+ "ADMIN", GetUserNameFromId(roleid, false))));
}
else
{
@@ -2188,7 +2271,9 @@ check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
- GetUserNameFromId(grantorId, false))));
+ GetUserNameFromId(grantorId, false)),
+ errdetail("Only roles with privileges of role \"%s\" may revoke privileges granted by this role.",
+ GetUserNameFromId(grantorId, false))));
}
/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 3506b77cc7..2293c0c6fc 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1143,7 +1143,9 @@ CheckSlotPermissions(void)
if (!has_rolreplication(GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to use replication slots")));
+ errmsg("permission denied to use replication slots"),
+ errdetail("Only roles with the %s attribute may use replication slots.",
+ "REPLICATION")));
}
/*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a9fb97460d..ea91ce355f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3883,7 +3883,9 @@ TerminateOtherDBBackends(Oid databaseId)
!has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("Only roles with privileges of the role whose process is being terminated or with privileges of the \"%s\" role may terminate this process.",
+ "pg_signal_backend")));
}
}
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index bc93ab5b52..eabb68a9e1 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -121,12 +121,16 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to cancel superuser query")));
+ errmsg("permission denied to cancel query"),
+ errdetail("Only roles with the %s attribute may cancel queries of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend")));
+ errmsg("permission denied to cancel query"),
+ errdetail("Only roles with privileges of the role whose query is being canceled or with privileges of the \"%s\" role may cancel this query.",
+ "pg_signal_backend")));
PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
}
@@ -223,12 +227,16 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
if (r == SIGNAL_BACKEND_NOSUPERUSER)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a superuser to terminate superuser process")));
+ errmsg("permission denied to terminate process"),
+ errdetail("Only roles with the %s attribute may terminate processes of roles with %s.",
+ "SUPERUSER", "SUPERUSER")));
if (r == SIGNAL_BACKEND_NOPERMISSION)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
+ errmsg("permission denied to terminate process"),
+ errdetail("Only roles with privileges of the role whose process is being terminated or with privileges of the \"%s\" role may terminate this process.",
+ "pg_signal_backend")));
/* Wait only on success and if actually requested */
if (r == SIGNAL_BACKEND_SUCCESS && timeout > 0)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c7d9d96b45..eada735363 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -950,7 +950,10 @@ standard_ProcessUtility(PlannedStmt *pstmt,
if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_checkpoint to do CHECKPOINT")));
+ errmsg("permission denied to execute %s command",
+ "CHECKPOINT"),
+ errdetail("Only roles with privileges of the \"%s\" role may execute this command.",
+ "pg_checkpoint")));
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 3026317bfc..92bac8b63f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -949,12 +949,14 @@ InitPostgres(const char *in_dbname, Oid dboid,
if (nfree < SuperuserReservedConnections)
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for superusers")));
+ errmsg("remaining connection slots are reserved for roles with %s",
+ "SUPERUSER")));
if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
- errmsg("remaining connection slots are reserved for roles with privileges of pg_use_reserved_connections")));
+ errmsg("remaining connection slots are reserved for roles with privileges of the \"%s\" role",
+ "pg_use_reserved_connections")));
}
/* Check replication permissions needed for walsender processes. */
@@ -965,7 +967,9 @@ InitPostgres(const char *in_dbname, Oid dboid,
if (!has_rolreplication(GetUserId()))
ereport(FATAL,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or replication role to start walsender")));
+ errmsg("permission denied to start WAL sender"),
+ errdetail("Only roles with the %s attribute may start a WAL sender process.",
+ "REPLICATION")));
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 51e07d5582..ea67cfa5e5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4209,8 +4209,9 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -4255,8 +4256,9 @@ GetConfigOptionResetString(const char *name)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.",
+ "pg_read_all_settings")));
switch (record->vartype)
{
@@ -5261,8 +5263,9 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
if (!ConfigOptionIsVisible(record))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
- name)));
+ errmsg("permission denied to examine \"%s\"", name),
+ errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.",
+ "pg_read_all_settings")));
if (varname)
*varname = record->name;
diff --git a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
index c57d4fd2df..de671e5a17 100644
--- a/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
+++ b/src/test/modules/dummy_seclabel/expected/dummy_seclabel.out
@@ -59,7 +59,8 @@ SECURITY LABEL ON ROLE regress_dummy_seclabel_user4 IS 'unclassified'; -- fail (
ERROR: role "regress_dummy_seclabel_user4" does not exist
SET SESSION AUTHORIZATION regress_dummy_seclabel_user2;
SECURITY LABEL ON ROLE regress_dummy_seclabel_user2 IS 'unclassified'; -- fail (not privileged)
-ERROR: must have CREATEROLE privilege
+ERROR: permission denied
+DETAIL: The current user must have the CREATEROLE attribute.
RESET SESSION AUTHORIZATION;
--
-- Test for various types of object
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index 88b1ff843b..61396b2a80 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,8 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: permission denied to examine "session_preload_libraries"
+DETAIL: Only roles with privileges of the "pg_read_all_settings" role may examine this parameter.
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 9f431bd4f5..5526e34a7f 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -7,26 +7,35 @@ CREATE ROLE regress_role_normal;
-- fail, CREATEROLE user can't give away role attributes without having them
SET SESSION AUTHORIZATION regress_role_limited_admin;
CREATE ROLE regress_nosuch_superuser SUPERUSER;
-ERROR: must be superuser to create superusers
+ERROR: permission denied to create role
+DETAIL: Only roles with the SUPERUSER attribute may create roles with SUPERUSER.
CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: Only roles with the REPLICATION attribute may create roles with REPLICATION.
CREATE ROLE regress_nosuch_replication REPLICATION;
-ERROR: must have replication permission to create replication users
+ERROR: permission denied to create role
+DETAIL: Only roles with the REPLICATION attribute may create roles with REPLICATION.
CREATE ROLE regress_nosuch_bypassrls BYPASSRLS;
-ERROR: must have bypassrls to create bypassrls users
+ERROR: permission denied to create role
+DETAIL: Only roles with the BYPASSRLS attribute may create roles with BYPASSRLS.
CREATE ROLE regress_nosuch_createdb CREATEDB;
-ERROR: must have createdb permission to create createdb users
+ERROR: permission denied to create role
+DETAIL: Only roles with the CREATEDB attribute may create roles with CREATEDB.
-- ok, can create a role without any special attributes
CREATE ROLE regress_role_limited;
-- fail, can't give it in any of the restricted attributes
ALTER ROLE regress_role_limited SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the SUPERUSER attribute may change the SUPERUSER attribute.
ALTER ROLE regress_role_limited REPLICATION;
-ERROR: must have replication privilege to change replication attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the REPLICATION attribute may change the REPLICATION attribute.
ALTER ROLE regress_role_limited CREATEDB;
-ERROR: must have createdb privilege to change createdb attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the CREATEDB attribute may change the CREATEDB attribute.
ALTER ROLE regress_role_limited BYPASSRLS;
-ERROR: must have bypassrls privilege to change bypassrls attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the BYPASSRLS attribute may change the BYPASSRLS attribute.
DROP ROLE regress_role_limited;
-- ok, can give away these role attributes if you have them
SET SESSION AUTHORIZATION regress_role_admin;
@@ -43,9 +52,11 @@ ALTER ROLE regress_createdb NOCREATEDB;
ALTER ROLE regress_createdb CREATEDB;
-- fail, can't toggle SUPERUSER
ALTER ROLE regress_createdb SUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the SUPERUSER attribute may change the SUPERUSER attribute.
ALTER ROLE regress_createdb NOSUPERUSER;
-ERROR: must be superuser to alter superuser roles or change superuser attribute
+ERROR: permission denied to alter role
+DETAIL: Only roles with the SUPERUSER attribute may change the SUPERUSER attribute.
-- ok, having CREATEROLE is enough to create users with these privileges
CREATE ROLE regress_createrole CREATEROLE NOINHERIT;
GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION;
@@ -59,7 +70,8 @@ CREATE ROLE regress_noiseword SYSID 12345;
NOTICE: SYSID can no longer be specified
-- fail, cannot grant membership in superuser role
CREATE ROLE regress_nosuch_super IN ROLE regress_role_super;
-ERROR: must be superuser to alter superusers
+ERROR: permission denied to grant role "regress_role_super"
+DETAIL: Only roles with the SUPERUSER attribute may grant roles with SUPERUSER.
-- fail, database owner cannot have members
CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner;
ERROR: role "pg_database_owner" cannot have explicit members
@@ -94,11 +106,14 @@ ALTER ROLE regress_hasprivs RENAME TO regress_tenant;
ALTER ROLE regress_tenant NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-- fail, we should be unable to modify a role we did not create
COMMENT ON ROLE regress_role_normal IS 'some comment';
-ERROR: must have admin option on role "regress_role_normal"
+ERROR: permission denied
+DETAIL: The current user must have the ADMIN option on role "regress_role_normal".
ALTER ROLE regress_role_normal RENAME TO regress_role_abnormal;
ERROR: permission denied to rename role
+DETAIL: Only roles with the CREATEROLE attribute and the ADMIN option on role "regress_role_normal" may rename this role.
ALTER ROLE regress_role_normal NOINHERIT NOLOGIN CONNECTION LIMIT 7;
-ERROR: permission denied
+ERROR: permission denied to alter role
+DETAIL: Only roles with the CREATEROLE attribute and the ADMIN option on role "regress_role_normal" may alter this role.
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
@@ -123,6 +138,7 @@ ERROR: must be able to SET ROLE "regress_tenant"
-- fail, we don't inherit permissions from regress_tenant
REASSIGN OWNED BY regress_tenant TO regress_createrole;
ERROR: permission denied to reassign objects
+DETAIL: Only roles with privileges of role "regress_tenant" may reassign objects owned by it.
-- ok, create a role with a value for createrole_self_grant
SET createrole_self_grant = 'set, inherit';
CREATE ROLE regress_tenant2;
@@ -150,25 +166,35 @@ ERROR: must be able to SET ROLE "regress_tenant2"
DROP TABLE tenant2_table;
-- fail, CREATEROLE is not enough to create roles in privileged roles
CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data;
-ERROR: must have admin option on role "pg_read_all_data"
+ERROR: permission denied to grant role "pg_read_all_data"
+DETAIL: Only roles with the ADMIN option on role "pg_read_all_data" may grant this role.
CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data;
-ERROR: must have admin option on role "pg_write_all_data"
+ERROR: permission denied to grant role "pg_write_all_data"
+DETAIL: Only roles with the ADMIN option on role "pg_write_all_data" may grant this role.
CREATE ROLE regress_monitor IN ROLE pg_monitor;
-ERROR: must have admin option on role "pg_monitor"
+ERROR: permission denied to grant role "pg_monitor"
+DETAIL: Only roles with the ADMIN option on role "pg_monitor" may grant this role.
CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings;
-ERROR: must have admin option on role "pg_read_all_settings"
+ERROR: permission denied to grant role "pg_read_all_settings"
+DETAIL: Only roles with the ADMIN option on role "pg_read_all_settings" may grant this role.
CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats;
-ERROR: must have admin option on role "pg_read_all_stats"
+ERROR: permission denied to grant role "pg_read_all_stats"
+DETAIL: Only roles with the ADMIN option on role "pg_read_all_stats" may grant this role.
CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables;
-ERROR: must have admin option on role "pg_stat_scan_tables"
+ERROR: permission denied to grant role "pg_stat_scan_tables"
+DETAIL: Only roles with the ADMIN option on role "pg_stat_scan_tables" may grant this role.
CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files;
-ERROR: must have admin option on role "pg_read_server_files"
+ERROR: permission denied to grant role "pg_read_server_files"
+DETAIL: Only roles with the ADMIN option on role "pg_read_server_files" may grant this role.
CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files;
-ERROR: must have admin option on role "pg_write_server_files"
+ERROR: permission denied to grant role "pg_write_server_files"
+DETAIL: Only roles with the ADMIN option on role "pg_write_server_files" may grant this role.
CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program;
-ERROR: must have admin option on role "pg_execute_server_program"
+ERROR: permission denied to grant role "pg_execute_server_program"
+DETAIL: Only roles with the ADMIN option on role "pg_execute_server_program" may grant this role.
CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend;
-ERROR: must have admin option on role "pg_signal_backend"
+ERROR: permission denied to grant role "pg_signal_backend"
+DETAIL: Only roles with the ADMIN option on role "pg_signal_backend" may grant this role.
-- fail, role still owns database objects
DROP ROLE regress_tenant;
ERROR: role "regress_tenant" cannot be dropped because some objects depend on it
@@ -211,11 +237,13 @@ DROP ROLE regress_inroles;
DROP ROLE regress_adminroles;
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
DROP ROLE regress_role_super;
-ERROR: must be superuser to drop superusers
+ERROR: permission denied to drop role
+DETAIL: Only roles with the SUPERUSER attribute may drop roles with SUPERUSER.
DROP ROLE regress_role_admin;
ERROR: current user cannot be dropped
DROP ROLE regress_rolecreator;
-ERROR: must have admin option on role "regress_rolecreator"
+ERROR: permission denied to drop role
+DETAIL: Only roles with the CREATEROLE attribute and the ADMIN option on role "regress_rolecreator" may drop this role.
-- ok
RESET SESSION AUTHORIZATION;
REVOKE CREATE ON DATABASE regression FROM regress_role_admin CASCADE;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 520035f6a0..2b96720e29 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -48,12 +48,16 @@ SET SESSION AUTHORIZATION regress_dep_user0;
-- permission denied
DROP OWNED BY regress_dep_user1;
ERROR: permission denied to drop objects
+DETAIL: Only roles with privileges of role "regress_dep_user1" may drop objects owned by it.
DROP OWNED BY regress_dep_user0, regress_dep_user2;
ERROR: permission denied to drop objects
+DETAIL: Only roles with privileges of role "regress_dep_user2" may drop objects owned by it.
REASSIGN OWNED BY regress_dep_user0 TO regress_dep_user1;
ERROR: permission denied to reassign objects
+DETAIL: Only roles with privileges of role "regress_dep_user1" may reassign objects to it.
REASSIGN OWNED BY regress_dep_user1 TO regress_dep_user0;
ERROR: permission denied to reassign objects
+DETAIL: Only roles with privileges of role "regress_dep_user1" may reassign objects owned by it.
-- this one is allowed
DROP OWNED BY regress_dep_user0;
CREATE TABLE deptest1 (f1 int unique);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 5496ec8f55..3cf4ac8c9e 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -37,7 +37,7 @@ CREATE ROLE regress_priv_role;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION;
GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2;
GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3;
-ERROR: admin option cannot be granted back to your own grantor
+ERROR: ADMIN option cannot be granted back to your own grantor
-- need CASCADE to revoke grant or admin option if dependent grants exist
REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail
ERROR: dependent privileges exist
@@ -156,7 +156,8 @@ ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate
NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1"
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted
-ERROR: must have CREATEROLE privilege to change another user's password
+ERROR: permission denied to alter role
+DETAIL: To change another role's password, the current user must have the CREATEROLE attribute and the ADMIN option on the role.
RESET SESSION AUTHORIZATION;
ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2;
REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1;
@@ -168,7 +169,8 @@ CREATE FUNCTION leak(integer,integer) RETURNS boolean
ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1;
-- test owner privileges
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION
-ERROR: grantor must have ADMIN OPTION on "regress_priv_role"
+ERROR: permission denied to grant privileges as role "regress_priv_role"
+DETAIL: The grantor must have the ADMIN option on role "regress_priv_role".
GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE;
REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error
ERROR: role "foo" does not exist
@@ -1795,7 +1797,8 @@ REFRESH MATERIALIZED VIEW sro_mv;
ERROR: cannot fire deferred trigger within security-restricted operation
CONTEXT: SQL function "mv_action" statement 1
BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
CONTEXT: SQL function "unwanted_grant" statement 1
SQL statement "SELECT unwanted_grant()"
PL/pgSQL function sro_trojan() line 1 at PERFORM
@@ -1825,10 +1828,12 @@ CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
SET SESSION AUTHORIZATION regress_priv_user1;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4"
dogrant_ok
@@ -1838,10 +1843,12 @@ NOTICE: role "regress_priv_user5" has already been granted membership in role "
SET ROLE regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
SET SESSION AUTHORIZATION regress_priv_group2;
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
-ERROR: must have admin option on role "regress_priv_group2"
+ERROR: permission denied to grant role "regress_priv_group2"
+DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
SET SESSION AUTHORIZATION regress_priv_user4;
DROP FUNCTION dogrant_ok();
REVOKE regress_priv_group2 FROM regress_priv_user5;
--
2.25.1
On 17.03.23 00:47, Nathan Bossart wrote:
Here is a rebased patch in which I've addressed the latest feedback except
for the DropRole() part that is under discussion.
committed
On Fri, Mar 17, 2023 at 10:40:06AM +0100, Peter Eisentraut wrote:
committed
Thanks!
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com