Thread: has_privs_of_role vs. is_member_of_role, redux

has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
Hi,

We've had some previous discussions about when to use
has_privs_of_role and when to use is_member_of_role, and
has_privs_of_role has mostly won the fight. That means that, if role
"robert" is set to NOINHERIT and you "GRANT stuff TO robert", for the
most part "robert" will not actually be able to do things that "stuff"
could do. Now, robert will be able TO "SET ROLE stuff" and then do all
and only those things that "stuff" can do, but he won't be able to do
those things as "robert". For example:

rhaas=# set role robert;
SET
rhaas=> select * from stuff_table;
ERROR:  permission denied for table stuff_table

So far, so good. But it's clearly not the case that "GRANT stuff TO
robert" has conferred no privileges at all on robert. At the very
least, it's enabled him to "SET ROLE stuff", but what else? I decided
to go through the code and make a list of the things that robert can
now do that he couldn't do before. Here it is:

1. robert can create new objects of various types owned by stuff:

rhaas=> create schema stuff_by_robert authorization stuff;
CREATE SCHEMA
rhaas=> create schema unrelated_by_robert authorization unrelated;
ERROR:  must be member of role "unrelated"

2. robert can change the owner of objects he owns to instead be owned by stuff:

rhaas=> alter table robert_table owner to unrelated;
ERROR:  must be member of role "unrelated"
rhaas=> alter table robert_table owner to stuff;
ALTER TABLE

3. robert can change the default privileges for stuff:

rhaas=> alter default privileges for role unrelated grant select on
tables to public;
ERROR:  must be member of role "unrelated"
rhaas=> alter default privileges for role stuff grant select on tables
to public;
ALTER DEFAULT PRIVILEGES

4. robert can execute "SET ROLE stuff".

That's it. There are two other behaviors that change -- the return
value of pg_has_role('robert', 'stuff', 'MEMBER') and pg_hba.conf
matching to groups -- but those aren't things that robert gains the
ability to do. The above is an exhaustive list of the things robert
gains the ability to do.

I argue that #3 is a clear bug. robert can't select from stuff's
tables or change privileges on stuff's objects, so why can he change
stuff's default privileges? is_member_of_role() has a note that it is
not to be used for privilege checking, and this seems like it's pretty
clearly a privilege check.

On the flip side, #4 is pretty clearly correct. Presumably, allowing
that to happen was the whole point of executing "GRANT stuff TO
robert" in the first place.

The other two are less clear, in my opinion. We don't want users to
end up owning objects that they didn't intend to own; in particular,
if any user could make a security-definer function and then gift it to
the superuser, it would be a disaster. So, arguably, the ability to
make some other role the owner of an object represents a privilege
that your role holds with respect to their role. Under that theory,
the is_member_of_role() checks that are performed in cases #1 and #2
are privilege checks, and we ought to be using has_privis_of_role()
instead, so that a non-inherited role grant doesn't confer those
privileges. But I don't find this very clear cut, because except when
the object you're gifting is a Trojan horse, giving stuff away helps
the recipient, not the donor.

Also, from a practical point of view, changing the owner of an object
is different from other things that robert might want to do. If robert
wants to create a table as user stuff or read some data from tables
user stuff can access or change privileges on objects that role stuff
owns, he can just execute "SET ROLE stuff" and then do any of that
stuff. But he can't give away his own objects by assuming stuff's
privileges. Either he can do it as himself, or he can't do it at all.
It wouldn't be crazy IMHO to decide that a non-inherited grant isn't
sufficient to donate objects to the granted role, and thus an
inherited grant is required in such cases. However, the current system
doesn't seem insane either, and in fact might be convenient in some
situations.

In short, my proposal is to change the ALTER DEFAULT PRIVILEGES code
so that you have to have the privileges of the target role, not jut
membership in the target role, and leave everything else unchanged.

Thoughts?

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Joe Conway
Date:
On 8/25/22 12:12, Robert Haas wrote:
> So far, so good. But it's clearly not the case that "GRANT stuff TO
> robert" has conferred no privileges at all on robert. At the very
> least, it's enabled him to "SET ROLE stuff", but what else? I decided
> to go through the code and make a list of the things that robert can
> now do that he couldn't do before. Here it is:
> 
> 1. robert can create new objects of various types owned by stuff:

> 2. robert can change the owner of objects he owns to instead be owned by stuff:

> 3. robert can change the default privileges for stuff:

> 4. robert can execute "SET ROLE stuff".

Nice analysis, and surprising (to me)

> I argue that #3 is a clear bug. robert can't select from stuff's
> tables or change privileges on stuff's objects, so why can he change
> stuff's default privileges? is_member_of_role() has a note that it is
> not to be used for privilege checking, and this seems like it's pretty
> clearly a privilege check.


+1 this feels very wrong to me


> On the flip side, #4 is pretty clearly correct. Presumably, allowing
> that to happen was the whole point of executing "GRANT stuff TO
> robert" in the first place.

Exactly

> The other two are less clear, in my opinion. We don't want users to
> end up owning objects that they didn't intend to own; in particular,
> if any user could make a security-definer function and then gift it to
> the superuser, it would be a disaster. So, arguably, the ability to
> make some other role the owner of an object represents a privilege
> that your role holds with respect to their role. Under that theory,
> the is_member_of_role() checks that are performed in cases #1 and #2
> are privilege checks, and we ought to be using has_privis_of_role()
> instead, so that a non-inherited role grant doesn't confer those
> privileges. But I don't find this very clear cut, because except when
> the object you're gifting is a Trojan horse, giving stuff away helps
> the recipient, not the donor.
> 
> Also, from a practical point of view, changing the owner of an object
> is different from other things that robert might want to do. If robert
> wants to create a table as user stuff or read some data from tables
> user stuff can access or change privileges on objects that role stuff
> owns, he can just execute "SET ROLE stuff" and then do any of that
> stuff. But he can't give away his own objects by assuming stuff's
> privileges. Either he can do it as himself, or he can't do it at all.
> It wouldn't be crazy IMHO to decide that a non-inherited grant isn't
> sufficient to donate objects to the granted role, and thus an
> inherited grant is required in such cases. However, the current system
> doesn't seem insane either, and in fact might be convenient in some
> situations.
> 
> In short, my proposal is to change the ALTER DEFAULT PRIVILEGES code
> so that you have to have the privileges of the target role, not jut
> membership in the target role, and leave everything else unchanged.
> 
> Thoughts?

I'm not sure about these last two. Does it matter that object creation 
is being logged, maybe for auditing purposes, under a different user 
than the owner of the object?

-- 
Joe Conway
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Thu, Aug 25, 2022 at 3:03 PM Joe Conway <mail@joeconway.com> wrote:
> Nice analysis, and surprising (to me)

Thanks.

> > I argue that #3 is a clear bug. robert can't select from stuff's
> > tables or change privileges on stuff's objects, so why can he change
> > stuff's default privileges? is_member_of_role() has a note that it is
> > not to be used for privilege checking, and this seems like it's pretty
> > clearly a privilege check.
>
> +1 this feels very wrong to me

Cool. I'll prepare a patch for that, unless someone else beats me to it.

I really hate back-patching this kind of change but it's possible that
it's the right thing to do. There's no real security exposure because
the member could always SET ROLE and then do the exact same thing, so
back-patching feels to me like it has a significantly higher chance of
turning happy users into unhappy ones than the reverse. On the other
hand, it's pretty hard to defend the current behavior once you stop to
think about it, so perhaps it should be back-patched on those grounds.
On the third hand, the fact that this has gone undiscovered for a
decade makes you wonder whether we've really had clear enough ideas
about this to justify calling it a bug rather than, say, an elevation
of our thinking on this topic.

> I'm not sure about these last two. Does it matter that object creation
> is being logged, maybe for auditing purposes, under a different user
> than the owner of the object?

I'd be inclined to say that it doesn't matter, because the grant could
have just as well been inheritable, or the action could have been
performed by a superuser. Also, as a rule of thumb, I don't think we
should choose to prohibit things on the grounds that some auditing
regime might not be able to understand what happened. If that's an
issue, we should address it by making the logging better, or including
better logging hooks, or what have you. I think that the focus should
be on the permissions model: what is the "right thing" from a security
standpoint?

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> I really hate back-patching this kind of change but it's possible that
> it's the right thing to do. There's no real security exposure because
> the member could always SET ROLE and then do the exact same thing, so
> back-patching feels to me like it has a significantly higher chance of
> turning happy users into unhappy ones than the reverse. On the other
> hand, it's pretty hard to defend the current behavior once you stop to
> think about it, so perhaps it should be back-patched on those grounds.
> On the third hand, the fact that this has gone undiscovered for a
> decade makes you wonder whether we've really had clear enough ideas
> about this to justify calling it a bug rather than, say, an elevation
> of our thinking on this topic.

Yeah, I'd lean against back-patching.  This is the sort of behavioral
change that users tend not to like finding in minor releases.

            regards, tom lane



Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Thu, Aug 25, 2022 at 4:41 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Yeah, I'd lean against back-patching.  This is the sort of behavioral
> change that users tend not to like finding in minor releases.

Here's a small patch. Despite the small size of the patch, there are a
couple of debatable points here:

1. Should we have a code comment? I feel it isn't necessary, because
there's a comment just a few lines earlier saying "Look up the role
OIDs and do permissions checks" and that seems like sufficient
justification for what follows.

2. What about the error message? Personally, I'm not very excited
about "permission denied to whatever" as a way to phrase an error
message. It doesn't sound like particularly good grammar to me. But
it's the phrasing we use elsewhere, so I guess we should do the same
here.

3. Should we have a test case? We are extremely thin on test cases for
NOINHERIT behavior, it seems, and testing this one thing when we don't
test anything else seems relatively useless. Also, privileges.sql is a
giant mess. It's a 1700+ line test file that tests many fairly
unrelated things. I am inclined to think that this file badly needs to
be split up into a bunch of smaller files, because it's practically
unmaintainable as is. For instance, the stuff at the top of the file
is testing a bunch of things about role privileges, but then check
some stuff about leakproof functions before coming back to test stuff
about groups, which logically probably belongs with the role
privileges stuff. Perhaps a reasonable starting split would be
something like:

- Privileges on roles.
- Privileges on relations.
- Privileges on other kinds of objects.
- Default privileges.
- Security barriers and leakproof functions.
- Security-restricted operations.

Some of those files might be fairly small initially, but they might be
get bigger later, especially because it'd be a heck of a lot easier to
add new test cases if you didn't have to worry that some change you
make is going to break a test 1000 lines down in the file.

-- 
Robert Haas
EDB: http://www.enterprisedb.com

Attachment

Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
Jeff Davis's comment in
http://postgr.es/m/4f8d536a9221bccc5a33bb784dace0ef2310ec4a.camel@j-davis.com
reminds me that I need to update this thread based on the patch posted
over there. That patch allows you to grant membership in one role to
another while withholding the ability to SET ROLE to the target role.
And it's already possible to grant membership in one role to another
while not allowing for inheritance of privileges. And I think that
sheds new light on the two debatable points from my original email:

On Thu, Aug 25, 2022 at 12:12 PM Robert Haas <robertmhaas@gmail.com> wrote:
> 1. robert can create new objects of various types owned by stuff:
>
> rhaas=> create schema stuff_by_robert authorization stuff;
> CREATE SCHEMA
> rhaas=> create schema unrelated_by_robert authorization unrelated;
> ERROR:  must be member of role "unrelated"
>
> 2. robert can change the owner of objects he owns to instead be owned by stuff:
>
> rhaas=> alter table robert_table owner to unrelated;
> ERROR:  must be member of role "unrelated"
> rhaas=> alter table robert_table owner to stuff;
> ALTER TABLE

It now seems to me that we should disallow these, because if we adopt
the patch from that other thread, and then you GRANT
pg_read_all_settings TO alice WITH INHERIT false, SET false, you might
reasonably expect that alice is not going to be able to clutter the
system with a bunch of objects owned by pg_read_all_settings, but
because of (1) and (2), alice can do exactly that.

To be more precise, I propose that in order for alice to create
objects owned by bob or to change one of her objects to be owned by
bob, she must not only be a member of role bob, but also inherit bob's
privileges. If she has the ability to SET ROLE bob but not does not
inherit his privileges, she can create new objects owned by bob only
if she first does SET ROLE bob, and she cannot reassign ownership of
her objects to bob at all.

Meanwhile, the patch that I posted previously to fix point (3) from
the original email, that ALTER DEFAULT PRIVILEGES is allowed for no
good reason, still seems like a good idea. Any reviews appreciated.

Thanks,

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> Jeff Davis's comment in
> http://postgr.es/m/4f8d536a9221bccc5a33bb784dace0ef2310ec4a.camel@j-davis.com
> reminds me that I need to update this thread based on the patch posted
> over there. That patch allows you to grant membership in one role to
> another while withholding the ability to SET ROLE to the target role.
> And it's already possible to grant membership in one role to another
> while not allowing for inheritance of privileges. And I think that
> sheds new light on the two debatable points from my original email:
>
> On Thu, Aug 25, 2022 at 12:12 PM Robert Haas <robertmhaas@gmail.com> wrote:
> > 1. robert can create new objects of various types owned by stuff:
> >
> > rhaas=> create schema stuff_by_robert authorization stuff;
> > CREATE SCHEMA
> > rhaas=> create schema unrelated_by_robert authorization unrelated;
> > ERROR:  must be member of role "unrelated"
> >
> > 2. robert can change the owner of objects he owns to instead be owned by stuff:
> >
> > rhaas=> alter table robert_table owner to unrelated;
> > ERROR:  must be member of role "unrelated"
> > rhaas=> alter table robert_table owner to stuff;
> > ALTER TABLE
>
> It now seems to me that we should disallow these, because if we adopt
> the patch from that other thread, and then you GRANT
> pg_read_all_settings TO alice WITH INHERIT false, SET false, you might
> reasonably expect that alice is not going to be able to clutter the
> system with a bunch of objects owned by pg_read_all_settings, but
> because of (1) and (2), alice can do exactly that.

Err, that shouldn't be allowed and if it is then that's my fault for not
implementing something to avoid having that happen.  imv, predefined
roles shouldn't be able to end up with objects they own except in cases
where we declare that a predefined role owns X.

I do think that the above two are correct and am fairly confident that
they were intentional as implemented as, otherwise, as noted in your
original message, you can't actually change the ownership of the
existing object/table and instead end up having to copy the whole thing,
which seems quite inefficient.  In other words, the same result could be
accomplished but in a much less efficient way and therefore it makes
sense to provide a way for it to be done that is efficient.

> To be more precise, I propose that in order for alice to create
> objects owned by bob or to change one of her objects to be owned by
> bob, she must not only be a member of role bob, but also inherit bob's
> privileges. If she has the ability to SET ROLE bob but not does not
> inherit his privileges, she can create new objects owned by bob only
> if she first does SET ROLE bob, and she cannot reassign ownership of
> her objects to bob at all.

... which means that to get a table owned by bob which is currently
owned by alice, alice has to:

set role bob;
create table;
grant insert on table to alice;
reset role;
insert into table select * from table;

That's pretty sucky and is the case that had been contemplated at the
time that was written to allow it (at least, if memory serves).  iirc,
that's also why we check the *bob* has CREATE rights in the place where
this is happening (as otherwise the above process wouldn't work either).

> Meanwhile, the patch that I posted previously to fix point (3) from
> the original email, that ALTER DEFAULT PRIVILEGES is allowed for no
> good reason, still seems like a good idea. Any reviews appreciated.

Haven't looked at the patch, +1 on the general change though, that looks
like incorrect usage of is_member_of_role to me.

Thanks,

Stephen

Attachment

Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Wed, Sep 7, 2022 at 5:51 PM Stephen Frost <sfrost@snowman.net> wrote:
> > To be more precise, I propose that in order for alice to create
> > objects owned by bob or to change one of her objects to be owned by
> > bob, she must not only be a member of role bob, but also inherit bob's
> > privileges. If she has the ability to SET ROLE bob but not does not
> > inherit his privileges, she can create new objects owned by bob only
> > if she first does SET ROLE bob, and she cannot reassign ownership of
> > her objects to bob at all.
>
> ... which means that to get a table owned by bob which is currently
> owned by alice, alice has to:
>
> set role bob;
> create table;
> grant insert on table to alice;
> reset role;
> insert into table select * from table;
>
> That's pretty sucky and is the case that had been contemplated at the
> time that was written to allow it (at least, if memory serves).  iirc,
> that's also why we check the *bob* has CREATE rights in the place where
> this is happening (as otherwise the above process wouldn't work either).

Sure. I think it comes down to what you think that the system
administrator intended to block by not allowing alice to inherit bob's
permissions. In existing releases, there's no facility to prevent
alice from doing SET ROLE bob, so the system administrator can't have
intended this as a security measure. But the system administrator
might have intended that alice shouldn't do anything that relies on
bob's permissions by accident, else she should have SET ROLE. And in
that case the intention is defeated by allowing the operation. Now,
you may well have in mind some other intention that the system
administrator could have had where allowing alice to perform this
operation without needing to inherit bob's permissions is sensible;
I'm not trying to say there is no such case. I don't know what it is,
though.

My first reaction was in the same ballpark as yours: what's the big
deal? But as I think about it more, I struggle to reconcile that
instinct with any specific use case.

Fairly obviously, my thinking here is biased by having written the
patch to allow restricting SET ROLE. If alice can neither inherit
bob's privileges nor SET ROLE bob, she had better not be able to
create objects owned by bob, because otherwise she can make a table,
add an expression index that calls a user-defined function, do stuff
until it needs to be autovacuumed, and then give it to bob, and boom,
exploit. But that doesn't mean that the is_member_of_role() tests here
have to be changed to has_privs_of_role(). They could be changed to
has_privs_of_role() || member_can_set_role(). And if the consensus is
to do it that way, I'm OK with that.

I'm just a little unconvinced that it's actually the best route. I
think that logic of the form "well Alice could just SET ROLE and do it
anyway" is weak -- and not only because of the patch to allow
restricting SET ROLE, but because AFAICT there is no point to the
INHERIT option in the first place unless it is to force you to issue
SET ROLE. That is literally the only thing it does. If we're going to
have weird exceptions where you don't have to SET ROLE after all, why
even have INHERIT in the first place?

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Wolfgang Walther
Date:
Robert Haas:
> Fairly obviously, my thinking here is biased by having written the
> patch to allow restricting SET ROLE. If alice can neither inherit
> bob's privileges nor SET ROLE bob, she had better not be able to
> create objects owned by bob, because otherwise she can make a table,
> add an expression index that calls a user-defined function, do stuff
> until it needs to be autovacuumed, and then give it to bob, and boom,
> exploit. But that doesn't mean that the is_member_of_role() tests here
> have to be changed to has_privs_of_role(). They could be changed to
> has_privs_of_role() || member_can_set_role(). And if the consensus is
> to do it that way, I'm OK with that.
> 
> I'm just a little unconvinced that it's actually the best route. I
> think that logic of the form "well Alice could just SET ROLE and do it
> anyway" is weak -- and not only because of the patch to allow
> restricting SET ROLE, but because AFAICT there is no point to the
> INHERIT option in the first place unless it is to force you to issue
> SET ROLE. That is literally the only thing it does. If we're going to
> have weird exceptions where you don't have to SET ROLE after all, why
> even have INHERIT in the first place?

I think to change the owner of an object from role A to role B, you just 
need a different "privilege" on that role B to "use" the role that way, 
which is distinct from INHERIT or SET ROLE "privileges".

When you are allowed to INHERIT a role, you are allowed to use the 
GRANTs that have been given to this role. When you are allowed to SET 
ROLE, then you are allowed to switch into this role. You could think of 
another "privilege", USAGE on a role, which would allow you to "use" 
this role as a target in a statement to change the owner of an object.

To change the owner for an object from role A to role B, you need:
- the privilege to ALTER the object, which is implied when you are A
- the privilege to "use" role B as a target

So basically the privilege to use role B as the new owner, is a 
privilege you have **on** the role object B, while the privilege to 
change the owner of an object is something you have **through** your 
membership in role A.

Up to v15, there were no separate privileges for this. You were either a 
member of a role or you were not. Now with INHERIT and maybe SET ROLE 
privileges/grant options, we can do two things:
- Keep the ability to use a role as a target in those statements as the 
most basic privilege on a role, that is implied by membership in that 
role and can't be taken away (currently the case), or
- invent a new privilege or grant option to allow changing that.

But mixing this with either INHERIT or SET ROLE doesn't make sense, imho.

Best

Wolfgang



Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Thu, Sep 8, 2022 at 11:45 AM Wolfgang Walther
<walther@technowledgy.de> wrote:
> I think to change the owner of an object from role A to role B, you just
> need a different "privilege" on that role B to "use" the role that way,
> which is distinct from INHERIT or SET ROLE "privileges".

It's not distinct, though, because if you can transfer ownership of a
table to another user, you can use that ability to gain the privileges
of that user.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Wolfgang Walther
Date:
Robert Haas:
>> I think to change the owner of an object from role A to role B, you just
>> need a different "privilege" on that role B to "use" the role that way,
>> which is distinct from INHERIT or SET ROLE "privileges".
> 
> It's not distinct, though, because if you can transfer ownership of a
> table to another user, you can use that ability to gain the privileges
> of that user.

Right, but the inverse is not neccessarily true, so you could have SET 
ROLE privileges, but not "USAGE" - and then couldn't change the owner of 
an object to this role.

USAGE is not a good term, because it implies "least amount of 
privileges", but in this case it's quite the opposite.

In any case, adding a grant option for SET ROLE, while keeping the 
required privileges for a transfer of ownership at the minimum 
(membership only), doesn't really make sense. I guess both threads 
should be discussed together?

Best

Wolfgang



Re: has_privs_of_role vs. is_member_of_role, redux

From
walther@technowledgy.de
Date:
Robert Haas:
> Fairly obviously, my thinking here is biased by having written the
> patch to allow restricting SET ROLE. If alice can neither inherit
> bob's privileges nor SET ROLE bob, she had better not be able to
> create objects owned by bob, because otherwise she can make a table,
> add an expression index that calls a user-defined function, do stuff
> until it needs to be autovacuumed, and then give it to bob, and boom,
> exploit. But that doesn't mean that the is_member_of_role() tests here
> have to be changed to has_privs_of_role(). They could be changed to
> has_privs_of_role() || member_can_set_role(). And if the consensus is
> to do it that way, I'm OK with that.

A different line of thought (compared to the "USAGE" privilege I 
discussed earlier), would be:
To transfer ownership of an object, you need two sets of privileges:
- You need to have the privilege to initiate a request to transfer 
ownership.
- You need to have the privilege to accept a request to transfer ownership.

Let's imagine there'd be such a request created temporarily, then when I 
start the process of changing ownership, I would have to change to the 
other role and then accept that request.

In theory, I could also inherit that privilege, but that's not how the 
system works today. By using is_member_of_role, the decision was already 
made that this should not depend on inheritance. What is left, is the 
ability to do it via SET ROLE only.

So it should not be has_privs_of_role() nor has_privs_of_role() || 
member_can_set_role(), as you suggested above, but rather just 
member_can_set_role() only. Of course, only in the context of the SET 
ROLE patch.

Basically, with that patch is_member_of_role() has to become 
member_can_set_role().

> I'm just a little unconvinced that it's actually the best route. I
> think that logic of the form "well Alice could just SET ROLE and do it
> anyway" is weak -- and not only because of the patch to allow
> restricting SET ROLE, but because AFAICT there is no point to the
> INHERIT option in the first place unless it is to force you to issue
> SET ROLE. That is literally the only thing it does. If we're going to
> have weird exceptions where you don't have to SET ROLE after all, why
> even have INHERIT in the first place?

As stated above, I don't think this is about INHERIT. INHERIT works fine 
both without the SET ROLE patch (and keeping is_member_of_role) and with 
the SET ROLE patch (and changing to member_can_set_role).

The exception is made, because there is no formal two-step process for 
requesting and accepting a transfer of ownership. Or alternatively: 
There is no exception, it's just that during the command to transfer 
ownership, the current role has to be changed temporarily to the 
accepting role. And that's the same as checking is_member_of_role or 
member_can_set_role, respectively.

Best

Wolfgang



Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Fri, Aug 26, 2022 at 10:11 AM Robert Haas <robertmhaas@gmail.com> wrote:
> Here's a small patch. Despite the small size of the patch, there are a
> couple of debatable points here:

Nobody's commented on this patch specifically, but it seemed like we
had consensus that ALTER DEFAULT PRIVILEGES was doing The Wrong Thing,
so I've pushed the patch I posted previously for that issue.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Thu, Sep 8, 2022 at 1:06 PM <walther@technowledgy.de> wrote:
> A different line of thought (compared to the "USAGE" privilege I
> discussed earlier), would be:
> To transfer ownership of an object, you need two sets of privileges:
> - You need to have the privilege to initiate a request to transfer
> ownership.
> - You need to have the privilege to accept a request to transfer ownership.
>
> Let's imagine there'd be such a request created temporarily, then when I
> start the process of changing ownership, I would have to change to the
> other role and then accept that request.
>
> In theory, I could also inherit that privilege, but that's not how the
> system works today. By using is_member_of_role, the decision was already
> made that this should not depend on inheritance. What is left, is the
> ability to do it via SET ROLE only.

I do not accept the argument that we've already made the decision that
this should not depend on inheritance. It's pretty clear that we
haven't thought carefully enough about which checks should depend only
on membership, and which ones should depend on inheritance. The patch
I committed just now to fix ALTER DEFAULT PRIVILEGES is one clear
example of where we've gotten that wrong. We also changed the way
predefined roles worked with inheritance not too long ago, so that
they started using has_privs_of_role() rather than
is_member_of_role(). Our past thinking on this topic has been fuzzy
enough that we can't really conclude that because something uses
is_member_of_role() now that's what it should continue to do in the
future. We are working to get from a messy situation where the rules
aren't consistent or understandable to one where they are, and that
may mean changing some things.

> So it should not be has_privs_of_role() nor has_privs_of_role() ||
> member_can_set_role(), as you suggested above, but rather just
> member_can_set_role() only. Of course, only in the context of the SET
> ROLE patch.

Now, having said that, this choice of behavior might have some
advantages. It would mean that you could GRANT pg_read_all_settings TO
someone WITH INHERIT TRUE, SET FALSE and that user would be able to
read all settings but would not be able to create objects owned by
pg_read_all_settings. It would also be upward-compatible with the
existing behavior, which is nice.

Well, maybe. Suppose that role A has been granted pg_read_all_settings
WITH INHERIT TRUE, SET TRUE and role B has been granted
pg_read_all_settings WITH INHERIT TRUE, SET FALSE. A can create a
table owned by pg_read_all_settings. If A does that, then B can now
create a trigger on that table and usurp the privileges of
pg_read_all_settings, after which B can now create any number of
objects owned by pg_read_all_settings. If A does not do that, though,
I think that with the proposed rule, B would have no way to create
objects owned by A. This is a bit unsatisfying. It seems like B should
either have the right to usurp pg_read_all_settings's privileges or
not, rather than maybe having that right depending on what some other
user chooses to do.

But maybe it's OK. It's hard to come up with perfect solutions here.
One could take the view that the issue here is that
pg_read_all_settings shouldn't have the right to create objects in the
first place, and that this INHERIT vs. SET ROLE distinction is just a
distraction. However, that would require accepting the idea that it's
possible for a role to lack privileges granted to PUBLIC, which also
sounds pretty unsatisfying. On the whole, I'm inclined to think it's
reasonable to suppose that if you want to grant a role to someone
without letting them create objects owned by that role, it should be a
role that doesn't own any existing objects either. Essentially, that's
legislating that predefined roles should be minimally privileged: they
should hold the ability to do whatever it is that they are there to do
(like read all settings) but not have any other privileges (like the
ability to do stuff to objects they own).

But maybe there's a better answer. Ideas/opinions welcome.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Wolfgang Walther
Date:
Robert Haas:
> Well, maybe. Suppose that role A has been granted pg_read_all_settings
> WITH INHERIT TRUE, SET TRUE and role B has been granted
> pg_read_all_settings WITH INHERIT TRUE, SET FALSE. A can create a
> table owned by pg_read_all_settings. If A does that, then B can now
> create a trigger on that table and usurp the privileges of
> pg_read_all_settings, after which B can now create any number of
> objects owned by pg_read_all_settings.

I'm not seeing how this is possible. A trigger function would run with 
the invoking user's privileges by default, right? So B would have to 
create a trigger with a SECURITY DEFINER function, which is owned by 
pg_read_all_settings to actually usurp the privileges of that role. But 
creating objects with that owner is exactly the thing B can't do.

What am I missing?

Best

Wolfgang



Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Sun, Sep 25, 2022 at 5:08 AM Wolfgang Walther
<walther@technowledgy.de> wrote:
> Robert Haas:
> > Well, maybe. Suppose that role A has been granted pg_read_all_settings
> > WITH INHERIT TRUE, SET TRUE and role B has been granted
> > pg_read_all_settings WITH INHERIT TRUE, SET FALSE. A can create a
> > table owned by pg_read_all_settings. If A does that, then B can now
> > create a trigger on that table and usurp the privileges of
> > pg_read_all_settings, after which B can now create any number of
> > objects owned by pg_read_all_settings.
>
> I'm not seeing how this is possible. A trigger function would run with
> the invoking user's privileges by default, right? So B would have to
> create a trigger with a SECURITY DEFINER function, which is owned by
> pg_read_all_settings to actually usurp the privileges of that role. But
> creating objects with that owner is exactly the thing B can't do.

Yeah, my statement before wasn't correct. It appears that alice can't
just usurp the privileges of pg_read_all_settings trivially, but she
can create a trigger on any preexisting table owned by
pg_read_all_settings and then anyone who performs an operation that
causes that trigger to fire is at risk:

rhaas=# create role alice;
CREATE ROLE
rhaas=# create table foo (a int, b text);
CREATE TABLE
rhaas=# alter table foo owner to pg_read_all_settings;
ALTER TABLE
rhaas=# grant pg_read_all_settings to alice;
GRANT ROLE
rhaas=# grant create on schema public to alice;
GRANT
rhaas=# set session authorization alice;
SET
rhaas=> create or replace function alice_function () returns trigger
as $$begin raise notice 'this trigger is running as %', current_user;
return null; end$$ language plpgsql;
CREATE FUNCTION
rhaas=> create trigger t1 before insert or update or delete on foo for
each row execute function alice_function();
CREATE TRIGGER
rhaas=> begin;
BEGIN
rhaas=*> insert into foo values (1, 'stuff');
NOTICE:  this trigger is running as alice
INSERT 0 0
rhaas=*> rollback;
ROLLBACK
rhaas=> reset session authorization;
RESET
rhaas=# begin;
BEGIN
rhaas=*# insert into foo values (1, 'stuff');
NOTICE:  this trigger is running as rhaas
INSERT 0 0
rhaas=*# rollback;
ROLLBACK

This shows that if rhaas (or whoever) performs DML on a table owned by
pg_read_all_settings, he might trigger arbitrary code written by alice
to run under his own user ID. Now, that hazard would exist anyway for
tables owned by alice, but now it also exists for any tables owned by
pg_read_all_settings. I'm not really sure how significant that is. If
you can create triggers as some other user and that user ever does
stuff as themselves, you can probably steal their privileges, because
they will probably eventually do DML on one of their own tables and
thereby execute your Trojan trigger. However, in the particular case
of pg_read_all_settings, the intent is probably that nobody would ever
run as that user, and there is probably also no reason to create
tables or other objects owned by that user. So maybe we really can say
that just blocking SET ROLE is enough.

I'm slightly skeptical of that conclusion because the whole thing just
feels a bit flimsy. Like, the whole idea that you can compromise your
account by inserting a row into somebody else's table feels a little
nuts to me. Triggers and row-level security policies make it easy to
do things that look safe and are actually very dangerous. I think
anyone would reasonably expect that calling a function owned by some
other user might be risky, because who knows what that function might
do, but it seems less obvious that accessing a table could execute
arbitrary code, yet it can. And it is even less obvious that creating
a table owned by one role might give some other role who inherits that
user's privileges to booby-trap that table in a way that might fool a
third user into doing something unsafe. But I have no idea what we
could reasonably do to improve the situation.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Wolfgang Walther
Date:
Robert Haas:
> This shows that if rhaas (or whoever) performs DML on a table owned by
> pg_read_all_settings, he might trigger arbitrary code written by alice
> to run under his own user ID. Now, that hazard would exist anyway for
> tables owned by alice, but now it also exists for any tables owned by
> pg_read_all_settings.

This hazard exists for all tables that alice has been granted the 
TRIGGER privilege on. While we prevent alice from creating tables that 
are owned by pg_read_all_settings, we do not prevent inheriting the 
TRIGGER privilege.

> I'm slightly skeptical of that conclusion because the whole thing just
> feels a bit flimsy. Like, the whole idea that you can compromise your
> account by inserting a row into somebody else's table feels a little
> nuts to me. Triggers and row-level security policies make it easy to
> do things that look safe and are actually very dangerous. I think
> anyone would reasonably expect that calling a function owned by some
> other user might be risky, because who knows what that function might
> do, but it seems less obvious that accessing a table could execute
> arbitrary code, yet it can. And it is even less obvious that creating
> a table owned by one role might give some other role who inherits that
> user's privileges to booby-trap that table in a way that might fool a
> third user into doing something unsafe. But I have no idea what we
> could reasonably do to improve the situation.

Right. This will always be the case when giving out the TRIGGER 
privilege on one of your tables to somebody else.

There is two kind of TRIGGER privileges: An explicitly GRANTed privilege 
and an implicit privilege, that is given to the table owner.

I think, when WITH INHERIT TRUE, SET FALSE is set, we should:
- Inherit all explicitly granted privileges
- Not inherit any DDL privileges implicitly given through ownership: 
CREATE, REFERENCES, TRIGGER.
- Inherit all other privileges implicitly given through ownership (DML + 
others)

Those implicit DDL privileges should be considered part of WITH SET 
TRUE. When you can't do SET ROLE x, then you can't act as the owner of 
any object owned by x.

Or to put it the other way around: Only allow implicit ownership 
privileges to be executed when the CURRENT_USER is the owner. But 
provide a shortcut, when you have the WITH SET TRUE option on a role, so 
that you don't need to do SET ROLE + CREATE TRIGGER, but can just do 
CREATE TRIGGER instead. This is similar to the mental model of 
"requesting and accepting a transfer of ownership" with an implicit SET 
ROLE built-in, that I used before.

Best

Wolfgang



Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Mon, Sep 26, 2022 at 12:16 PM Wolfgang Walther
<walther@technowledgy.de> wrote:
> I think, when WITH INHERIT TRUE, SET FALSE is set, we should:
> - Inherit all explicitly granted privileges
> - Not inherit any DDL privileges implicitly given through ownership:
> CREATE, REFERENCES, TRIGGER.
> - Inherit all other privileges implicitly given through ownership (DML +
> others)

I don't think we're going to be very happy if we redefine inheriting
the privileges of another role to mean inheriting only some of them.
That seems pretty counterintuitive to me. I also think that this
particular definition is pretty fuzzy.

Your previous proposal was to make the SET attribute of a GRANT
control not only the ability to SET ROLE to the target role but also
the ability to create objects owned by that role and/or transfer
objects to that role. I think some people might find that behavior a
little bit surprising - certainly, it goes beyond what the name SET
implies - but it is at least simple enough to explain in one sentence,
and the consequences don't seem too difficult to reason about.

Here, though, it doesn't really seem simple enough to explain in one
sentence, nor does it seem easy to reason about. I'm not sure that
there's any firm distinction between DML privileges and DDL
privileges. I'm not sure that the privileges that you mention are all
and only those that are security-relevant, nor that it would
necessarily remain true in the future even if it's true today.

There are many operations which are permitted or declined just using
an owner-check. One example is commenting on an object. That sure
sounds like it would fit within your proposed "DDL privileges
implicitly given through ownership" category, but it doesn't really
present any security hazard, so I do not think there is a good reason
to restrict that from a user who has INHERIT TRUE, SET FALSE. Another
is renaming an object, which is a little more murky. You can't
directly usurp someone's privileges by renaming an object that they
own, but you could potentially rename an object out of the way and
replace it with one that you own and thus induce a user to do
something dangerous. I don't really want to make even small exceptions
to the idea that inheriting a role's privileges means inheriting all
of them, and I especially don't want to make large exceptions, or
exceptions that involve judgement calls about the relative degree of
risk of each possible operation.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Wolfgang Walther
Date:
Robert Haas:
> I don't think we're going to be very happy if we redefine inheriting
> the privileges of another role to mean inheriting only some of them.
> That seems pretty counterintuitive to me. I also think that this
> particular definition is pretty fuzzy.

Scratch my previous suggestion. A new, less fuzyy definition would be: 
Ownership is not a privilege itself and as such not inheritable.

When role A is granted to role B, two things happen:
1. Role B now has the right to use the GRANTed privileges of role A.
2. Role B now has the right to become role A via SET ROLE.

WITH SET controls whether point 2 is the case or not.

WITH INHERIT controls whether role B actually executes their right to 
use those privileges ("inheritance") **and** whether the set role is 
done implicitly for anything that requires ownership, but of course only 
WITH SET TRUE.

This is the same way that the role attributes INHERIT / NOINHERIT behave.

> Your previous proposal was to make the SET attribute of a GRANT
> control not only the ability to SET ROLE to the target role but also
> the ability to create objects owned by that role and/or transfer
> objects to that role. I think some people might find that behavior a
> little bit surprising - certainly, it goes beyond what the name SET
> implies - but it is at least simple enough to explain in one sentence,
> and the consequences don't seem too difficult to reason about.

This would be included in the above.

> Here, though, it doesn't really seem simple enough to explain in one
> sentence, nor does it seem easy to reason about.

I think the "ownership is not inheritable" idea is easy to explain.

> There are many operations which are permitted or declined just using
> an owner-check. One example is commenting on an object. That sure
> sounds like it would fit within your proposed "DDL privileges
> implicitly given through ownership" category, but it doesn't really
> present any security hazard, so I do not think there is a good reason
> to restrict that from a user who has INHERIT TRUE, SET FALSE. Another
> is renaming an object, which is a little more murky. You can't
> directly usurp someone's privileges by renaming an object that they
> own, but you could potentially rename an object out of the way and
> replace it with one that you own and thus induce a user to do
> something dangerous. I don't really want to make even small exceptions
> to the idea that inheriting a role's privileges means inheriting all
> of them, and I especially don't want to make large exceptions, or
> exceptions that involve judgement calls about the relative degree of
> risk of each possible operation.

I would not make this about security-risks only. We didn't distinguish 
between privileges and ownership that much before, because we didn't 
have WITH INHERIT or WITH SET. Now that we have both, we could do so.

The ideas of "inherited GRANTs" and "a shortcut to avoid SET ROLE to do 
owner-things" should be better to explain.

No judgement required.

All of this is to find a way to make WITH INHERIT TRUE, SET FALSE a 
"real", risk-free thing - and not just some syntactic sugar. And if that 
comes with the inability to COMMENT ON TABLE 
owned_by_pg_read_all_settings... fine. No need for that at all.

However, it would come with the inability to do SELECT * FROM 
owned_by_pg_read_all_settings, **unless** explicitly GRANTed to the 
owner, too. This might feel strange at first, but should not be a 
problem either. WITH INHERIT TRUE, SET FALSE is designed for built-in 
roles or other container roles that group a set of privileges. Those 
roles should not have objects they own anyway. And if they still do, 
denying access to those objects unless explicitly granted is the safe way.

Best

Wolfgang



Re: has_privs_of_role vs. is_member_of_role, redux

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Thu, Sep 8, 2022 at 1:06 PM <walther@technowledgy.de> wrote:
> > In theory, I could also inherit that privilege, but that's not how the
> > system works today. By using is_member_of_role, the decision was already
> > made that this should not depend on inheritance. What is left, is the
> > ability to do it via SET ROLE only.
>
> I do not accept the argument that we've already made the decision that
> this should not depend on inheritance. It's pretty clear that we
> haven't thought carefully enough about which checks should depend only
> on membership, and which ones should depend on inheritance. The patch
> I committed just now to fix ALTER DEFAULT PRIVILEGES is one clear
> example of where we've gotten that wrong. We also changed the way
> predefined roles worked with inheritance not too long ago, so that
> they started using has_privs_of_role() rather than
> is_member_of_role(). Our past thinking on this topic has been fuzzy
> enough that we can't really conclude that because something uses
> is_member_of_role() now that's what it should continue to do in the
> future. We are working to get from a messy situation where the rules
> aren't consistent or understandable to one where they are, and that
> may mean changing some things.

Agreed that we haven't been good about the distinction between these,
but that the recent work by Joshua and yourself has been moving us in
the right direction.

> One could take the view that the issue here is that
> pg_read_all_settings shouldn't have the right to create objects in the
> first place, and that this INHERIT vs. SET ROLE distinction is just a
> distraction. However, that would require accepting the idea that it's
> possible for a role to lack privileges granted to PUBLIC, which also
> sounds pretty unsatisfying. On the whole, I'm inclined to think it's
> reasonable to suppose that if you want to grant a role to someone
> without letting them create objects owned by that role, it should be a
> role that doesn't own any existing objects either. Essentially, that's
> legislating that predefined roles should be minimally privileged: they
> should hold the ability to do whatever it is that they are there to do
> (like read all settings) but not have any other privileges (like the
> ability to do stuff to objects they own).

Predefined roles are special in that they should GRANT just the
privileges that the role is described to GRANT and that users really
shouldn't be able to SET ROLE to them nor should they be allowed to own
objects, or at least that's my general feeling on them.

If an administrator doesn't wish for a user to have the privileges
provided by the predefined role by default, they should be able to set
that up by creating another role who has that privilege which the user
is able to SET ROLE to.  That is:

CREATE ROLE admin WITH INHERIT FALSE;
GRANT pg_read_all_settings TO admin;
GRANT admin TO alice;

Would allow 'alice' to log in without the privileges associated with
pg_read_all_settings but 'alice' is able to SET ROLE admin; and gain
those privileges.  It wasn't intended that 'alice' be able to SET ROLE
to pg_read_all_settings itself though.

* Robert Haas (robertmhaas@gmail.com) wrote:
> Yeah, my statement before wasn't correct. It appears that alice can't
> just usurp the privileges of pg_read_all_settings trivially, but she
> can create a trigger on any preexisting table owned by
> pg_read_all_settings and then anyone who performs an operation that
> causes that trigger to fire is at risk:

Triggers aren't the only thing to be worried about in this area-
functions defined inside of views are also run with the privileges of
the user running the SELECT and not as the owner of the view.  The same
is true of running SELECT against tables with RLS too, of course.
Generally speaking, it's always been very risky to access the objects of
users who you don't trust in any way and we don't currently provide any
particularly easy way to make that kind of access safe.  RLS at least
provides an escape by allowing a user to turn it off, but the same isn't
available for setting a search_path and then running queries or
accessing views or running DML against tables with triggers.

> I'm slightly skeptical of that conclusion because the whole thing just
> feels a bit flimsy. Like, the whole idea that you can compromise your
> account by inserting a row into somebody else's table feels a little
> nuts to me. Triggers and row-level security policies make it easy to
> do things that look safe and are actually very dangerous. I think
> anyone would reasonably expect that calling a function owned by some
> other user might be risky, because who knows what that function might
> do, but it seems less obvious that accessing a table could execute
> arbitrary code, yet it can. And it is even less obvious that creating
> a table owned by one role might give some other role who inherits that
> user's privileges to booby-trap that table in a way that might fool a
> third user into doing something unsafe. But I have no idea what we
> could reasonably do to improve the situation.

Just to reiterate- this is not only about DML/triggers or RLS but also
includes SELECT statements against views and setting of the search_path
to that owned by someone trying to compromise your account (though the
latter does require a bit more than just the SET itself).

One approach to dealing with this would be to have a mechanism to define
exactly what code you feel comfortable running and set that to be only
the bootstrap superuser (or perhaps we'd have this as a superuser-set
GUC list) and the current role and then fail any queries that end up
calling code owned by any other role.

* Wolfgang Walther (walther@technowledgy.de) wrote:
> Robert Haas:
> > This shows that if rhaas (or whoever) performs DML on a table owned by
> > pg_read_all_settings, he might trigger arbitrary code written by alice
> > to run under his own user ID. Now, that hazard would exist anyway for
> > tables owned by alice, but now it also exists for any tables owned by
> > pg_read_all_settings.
>
> This hazard exists for all tables that alice has been granted the TRIGGER
> privilege on. While we prevent alice from creating tables that are owned by
> pg_read_all_settings, we do not prevent inheriting the TRIGGER privilege.

The issue here is that we don't prevent alice from issuing a 'SET' to
pg_read_all_settings nor do we prevent predefined roles from creating
objects.  I'd be inclined to change the system to explicitly prevent
both of those things from being allowed- for the special case of
predefined roles and not as some general role capability.  Maybe there's
an argument that we should allow administrators to create roles that no
user is allowed to SET to or which aren't allowed to create/own objects
but I'm not sure that there's a strong use-case for that.  I do think
it's useful to allow administrators to create roles that *some* users
are allowed to have the privileges are but aren't allowed to SET to, but
the whole point of predefined roles is to use the role GRANT system as a
more flexible way to give out certain distinct privileges to certain
roles and that's it.

> > I'm slightly skeptical of that conclusion because the whole thing just
> > feels a bit flimsy. Like, the whole idea that you can compromise your
> > account by inserting a row into somebody else's table feels a little
> > nuts to me. Triggers and row-level security policies make it easy to
> > do things that look safe and are actually very dangerous. I think
> > anyone would reasonably expect that calling a function owned by some
> > other user might be risky, because who knows what that function might
> > do, but it seems less obvious that accessing a table could execute
> > arbitrary code, yet it can. And it is even less obvious that creating
> > a table owned by one role might give some other role who inherits that
> > user's privileges to booby-trap that table in a way that might fool a
> > third user into doing something unsafe. But I have no idea what we
> > could reasonably do to improve the situation.
>
> Right. This will always be the case when giving out the TRIGGER privilege on
> one of your tables to somebody else.

Giving out of the TRIGGER privilege is really just a ill-conceived idea
that we should acknowledge only exists because it's part of the
standard.

> There is two kind of TRIGGER privileges: An explicitly GRANTed privilege and
> an implicit privilege, that is given to the table owner.

That's really only half-right: TRIGGER is just one of the privileges
that the owner has if there's no ACL on the table.  If the ACL is
defined and the owner's entry has the TRIGGER privilege removed then
they'll lose the ability to create triggers on that table.  Of course,
the owner can simply GRANT that ability back to themselves if they wish
but it's a useful distinction to be aware of.

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Mon, Sep 26, 2022 at 12:16 PM Wolfgang Walther
> <walther@technowledgy.de> wrote:
> > I think, when WITH INHERIT TRUE, SET FALSE is set, we should:
> > - Inherit all explicitly granted privileges
> > - Not inherit any DDL privileges implicitly given through ownership:
> > CREATE, REFERENCES, TRIGGER.
> > - Inherit all other privileges implicitly given through ownership (DML +
> > others)
>
> I don't think we're going to be very happy if we redefine inheriting
> the privileges of another role to mean inheriting only some of them.
> That seems pretty counterintuitive to me. I also think that this
> particular definition is pretty fuzzy.

I agree with Robert on this part.  Inheriting the privileges of another
role should generally mean exactly that and not some awkward subset of
the privileges.

> Your previous proposal was to make the SET attribute of a GRANT
> control not only the ability to SET ROLE to the target role but also
> the ability to create objects owned by that role and/or transfer
> objects to that role. I think some people might find that behavior a
> little bit surprising - certainly, it goes beyond what the name SET
> implies - but it is at least simple enough to explain in one sentence,
> and the consequences don't seem too difficult to reason about.

I still feel it's useful to allow users to transfer object to roles that
they can SET to even if they don't inherit the privileges of that role.
I don't feel that should be allowed for predefined roles, however.

One thing that I don't want to miss mentioning is that I'm not against
the idea of predefined roles having ownership of some objects- but
should that happen (tho I tend to doubt it will, because we usually use
the bootstrap superuser for objects that admins can use but shouldn't be
mucking around with and changing), those objects shouldn't be ones that
are able to be messed with by anyone except a superuser running around
with allow_system_table_mods or such.

Thanks,

Stephen

Attachment

Re: has_privs_of_role vs. is_member_of_role, redux

From
Stephen Frost
Date:
Greetings,

* Wolfgang Walther (walther@technowledgy.de) wrote:
> Robert Haas:
> > I don't think we're going to be very happy if we redefine inheriting
> > the privileges of another role to mean inheriting only some of them.
> > That seems pretty counterintuitive to me. I also think that this
> > particular definition is pretty fuzzy.
>
> Scratch my previous suggestion. A new, less fuzyy definition would be:
> Ownership is not a privilege itself and as such not inheritable.

One of the reasons the role system was brought into being was explicitly
to allow other roles to have ownership-level rights on objects that they
didn't directly own.

I don't see us changing that.

Thanks,

Stephen

Attachment

Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Mon, Sep 26, 2022 at 3:16 PM Wolfgang Walther
<walther@technowledgy.de> wrote:
> Robert Haas:
> > I don't think we're going to be very happy if we redefine inheriting
> > the privileges of another role to mean inheriting only some of them.
> > That seems pretty counterintuitive to me. I also think that this
> > particular definition is pretty fuzzy.
>
> Scratch my previous suggestion. A new, less fuzyy definition would be:
> Ownership is not a privilege itself and as such not inheritable.
>
> When role A is granted to role B, two things happen:
> 1. Role B now has the right to use the GRANTed privileges of role A.
> 2. Role B now has the right to become role A via SET ROLE.
>
> WITH SET controls whether point 2 is the case or not.
>
> WITH INHERIT controls whether role B actually executes their right to
> use those privileges ("inheritance") **and** whether the set role is
> done implicitly for anything that requires ownership, but of course only
> WITH SET TRUE.

If I'm understanding correctly, this would amount to a major
redefinition of what it means to inherit privileges, and I think the
chances of such a change being accepted are approximately zero.
Inheriting privileges needs to keep meaning what it means now, namely,
you inherit all the rights of the granted role.

> > Here, though, it doesn't really seem simple enough to explain in one
> > sentence, nor does it seem easy to reason about.
>
> I think the "ownership is not inheritable" idea is easy to explain.

I don't. And even if I did think it were easy to explain, I don't
think it would be a good idea. One of my first patches to PostgreSQL
added a grantable TRUNCATE privilege to tables. I think that, under
your proposed definitions, the addition of this privilege would have
had the result that a role grant would cease to allow the recipient to
truncate tables owned by the granted role. There is currently a
proposal on the table to make VACUUM and ANALYZE grantable permissions
on tables, which would have the same issue. I think that if I made it
so that adding such privileges resulted in role inheritance not
working for those operations any more, people would come after me with
pitchforks. And I wouldn't blame them: that sounds terrible.

I think the only thing we should be discussing here is how to tighten
up the tests for operations in categories (1) and (2) in my original
email. The options so far proposed are: (a) do nothing, which makes
the proposed SET option on grants a lot less useful; (b) restrict
those operations by has_privs_of_role(), basically making them
dependent on the INHERIT option, (c) restrict them by
has_privs_of_role() || member_can_set_role(), requiring either the
INHERIT option or the SET option, or (d) restrict them by
member_can_set_role() only, i.e. making them depend on the SET  option
alone. A broader reworking of what the INHERIT option means is not on
the table: I don't want to write a patch for it, I don't think it's a
good idea, and I don't think the community would accept it even if I
did want to write a patch for it and even if I did think it was a good
idea.

I would like to hear more opinions on that topic. I understand your
vote from among those four options to be (d). I do not know what
anyone else thinks.

Thanks,

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Wolfgang Walther
Date:
Robert Haas:
>> Scratch my previous suggestion. A new, less fuzyy definition would be:
>> Ownership is not a privilege itself and as such not inheritable.
>> [...]
> If I'm understanding correctly, this would amount to a major
> redefinition of what it means to inherit privileges, and I think the
> chances of such a change being accepted are approximately zero.
> Inheriting privileges needs to keep meaning what it means now, namely,
> you inherit all the rights of the granted role.

No. Inheriting stays the same, it's just WITH SET that's different from 
what it is "now".

> I don't. And even if I did think it were easy to explain, I don't
> think it would be a good idea. One of my first patches to PostgreSQL
> added a grantable TRUNCATE privilege to tables. I think that, under
> your proposed definitions, the addition of this privilege would have
> had the result that a role grant would cease to allow the recipient to
> truncate tables owned by the granted role. There is currently a
> proposal on the table to make VACUUM and ANALYZE grantable permissions
> on tables, which would have the same issue. I think that if I made it
> so that adding such privileges resulted in role inheritance not
> working for those operations any more, people would come after me with
> pitchforks. And I wouldn't blame them: that sounds terrible.

No, there is a misunderstanding. In my proposal, when you do WITH SET 
TRUE everything stays exactly the same as it is right now.

I'm just saying WITH SET FALSE should take away more of the things you 
can do (all the ownership things) to a point where it's safe to GRANT .. 
WITH INHERIT TRUE, SET FALSE and still be useful for pre-defined or 
privilege-container roles.

Could be discussed in the WITH SET thread, but it's a natural extension 
of the categories (1) and (2) in your original email. It's all about 
ownership.

Best

Wolfgang



Re: has_privs_of_role vs. is_member_of_role, redux

From
Robert Haas
Date:
On Tue, Sep 27, 2022 at 2:05 AM Wolfgang Walther
<walther@technowledgy.de> wrote:
> I'm just saying WITH SET FALSE should take away more of the things you
> can do (all the ownership things) to a point where it's safe to GRANT ..
> WITH INHERIT TRUE, SET FALSE and still be useful for pre-defined or
> privilege-container roles.

I don't see that as viable, either. It's too murky what you'd have to
take away to make it safe, and it sounds like stuff that naturally
falls under INHERIT rather than SET.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: has_privs_of_role vs. is_member_of_role, redux

From
Jeff Davis
Date:
On Mon, 2022-09-19 at 15:32 -0400, Robert Haas wrote:
> One could take the view that the issue here is that
> pg_read_all_settings shouldn't have the right to create objects in
> the
> first place, and that this INHERIT vs. SET ROLE distinction is just a
> distraction. However, that would require accepting the idea that it's
> possible for a role to lack privileges granted to PUBLIC, which also
> sounds pretty unsatisfying. On the whole, I'm inclined to think it's
> reasonable to suppose that if you want to grant a role to someone
> without letting them create objects owned by that role, it should be
> a
> role that doesn't own any existing objects either. Essentially,
> that's
> legislating that predefined roles should be minimally privileged:
> they
> should hold the ability to do whatever it is that they are there to
> do
> (like read all settings) but not have any other privileges (like the
> ability to do stuff to objects they own).

I like this approach -- the idea that you can create a role that can't
own anything, can't create anything, and to which nobody else can "SET
ROLE".

Creating a "virtual" role like that feels much more declarative and
easy to document: "this isn't a real user, it's just a collection of
inheritable privileges". Even superusers couldn't "SET ROLE
pg_read_all_settings" or "OWNER TO pg_signal_backend".

I wouldn't call it "minimally privileged" (which feels wrong because it
wouldn't even have privileges on PUBLIC, as you say); I'd just say that
it's a type of role where those things just don't make sense.

--
Jeff Davis
PostgreSQL Contributor Team - AWS





Re: has_privs_of_role vs. is_member_of_role, redux

From
Jeff Davis
Date:
On Mon, 2022-09-26 at 15:40 -0400, Stephen Frost wrote:
> Predefined roles are special in that they should GRANT just the
> privileges that the role is described to GRANT and that users really
> shouldn't be able to SET ROLE to them nor should they be allowed to
> own
> objects, or at least that's my general feeling on them.

What about granting privileges to others? I don't think that makes
sense for a predefined role, either, because then they'd own a bunch of
grants, which is as awkward as owning objects.

> If an administrator doesn't wish for a user to have the privileges
> provided by the predefined role by default, they should be able to
> set
> that up by creating another role who has that privilege which the
> user
> is able to SET ROLE to.

And that other role could be used for grants, if needed, too.

But I don't think we need to special-case predefined roles though. I
think a lot of administrators would like to declare some roles that are
just a collection of inheritable privileges.


--
Jeff Davis
PostgreSQL Contributor Team - AWS