Re: Delegating superuser tasks to new security roles (Was: Granting control of SUSET gucs to non-superusers) - Mailing list pgsql-hackers

From Mark Dilger
Subject Re: Delegating superuser tasks to new security roles (Was: Granting control of SUSET gucs to non-superusers)
Date
Msg-id C95C50C4-9FD6-491B-AFB0-2C5B90FDCE1A@enterprisedb.com
Whole thread Raw
In response to Re: Delegating superuser tasks to new security roles (Was: Granting control of SUSET gucs to non-superusers)  (Stephen Frost <sfrost@snowman.net>)
List pgsql-hackers

> On Jul 23, 2021, at 9:20 AM, Stephen Frost <sfrost@snowman.net> wrote:
>
> These considerations were addressed with row_security by allowing the
> GUC to be set by anyone, but throwing an ERROR if RLS would have been
> required by the query instead of just allowing it.  I don't see any
> obvious reason why that couldn't be the case for event triggers..?

Because a postgres-as-a-service provider may want to install their own event triggers as well as allowing the customer
todo so, and it seems too coarse grained to either skip all of them or none of them.  It's perfectly reasonable to want
toskip your customer's event triggers while not skipping your own. 

>> A superuser-only GUC for this is also a bit too heavy handed.  The superuser may not want to circumvent all event
triggers,just those put in place by the pg_database_security role.  If that sounds arbitrary, just consider the
postgres-as-a-servicecase.  The superuser wants to be able to grant pg_database_security to the customer, but doesn't
wantthe customer to be able to use that to trap the service provider. 
>
> Having a trust system for triggers, functions, etc, where you can say
> whose triggers you're ok running might be interesting but it also seems
> like an awful lot of work and I'm not sure that it's actually really
> that much better than a GUC similar to row_security.

My first impression was that it is too much work, which is why I put event trigger creation into the pg_host_security
bucket. It might be more sane to just leave it as superuser-only.  But if we're going to fix this and make it a
pg_database_securityusable feature, then I think we need to solve the problems a naive approach would create for
serviceproviders. 

>> Instead of a GUC, how about checking permissions inside event triggers for both the user firing the trigger *and*
thetrigger owner.  That's a backward compatibility break, but maybe not a bad one, since until now only superusers have
beenallowed to create event triggers.  Systems which create an event trigger using a role that later has superuser
revoked,or which change ownership to a non-superuser, will see a behavior change.  I'm not super happy with that, but I
thinkit is better than the GUC based solution.  Event triggers owned by a superuser continue to work as they do now.
Eventtriggers owned by a non-superuser cannot be used to force a privileged user to run a command that the event
triggerowner could not have run for themself. 
>
> I'm not following what this suggestion is, exactly.  What permissions
> are being checked inside the event trigger being run, exactly..?  Who
> would get to set those permissions?  Any object owner, today, can GRANT
> access to any other user in the system, we don't prevent that.

I don't think GRANT is really relevant here, as what I'm trying to avoid is a less privileged user trapping a more
privilegeduser into running a function that the less privileged user can't directly run.  Certainly such a user cannot
GRANTprivilege on a function that they cannot even run, else your system has a privilege escalation hazard already.  

It's a substantial change to the security model, but the idea is that inside an event trigger, we'd
SetUserIdAndSecContextto a new type of context similar to SECURITY_LOCAL_USERID_CHANGE but where instead of simply
changingto the owner of the event trigger, we'd be changing to a virtual user who is defined to only have the
privilegesof the intersection of the current user and the event trigger owner.  That entails at least two problems,
thoughI don't see that they are insoluble.  First, all places in the code that check permissions need to check in a way
thatworks in this mode.  We might not be able to call GetUserId() as part of aclchecks any longer, and instead have to
callsome new function GetVirtualUserId() as part of aclchecks, reserving GetUserId() just for cases where you're not
tryingto perform a permissions check.  Second, since event triggers can cause other event triggers to fire, we'd need
thesevirtual users to be able to nest, so we'd have to push a stack of users and check all of them in each case, and
we'dhave to think carefully about how to handle errors, since GetUserIdAndSecContext and SetUserIdAndSecContext are
calledinside transaction start and end, where errors must not be raised.  But since transaction start and end would
neverwant to set or reset the state to a virtual user, those should never throw, and the calls elsewhere are at liberty
tothrow if they like, so we'd just have to be careful to use the right version of these operations in the right places. 

This all sounds a bit much, but it has knock-on benefits.  We'd be able to preserve the historical behavior of table
triggerswhile having a mode that behaves in this new way, which means that privileged users could be a bit more
cavalierthan they can now about DML against user defined tables.  This mode might become the standard operating mode
forservice provider scripts, whether running as superuser, as pg_network_security, or whatever.  This might also be
usedto secure operations on indexes over user defined functions.  If the index operation is run in this mode, the index
operationcould throw an error rather than performing a function maliciously embedded inside the index function call. 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






pgsql-hackers by date:

Previous
From: Tom Lane
Date:
Subject: Re: Configure with thread sanitizer fails the thread test
Next
From: "Bossart, Nathan"
Date:
Subject: Re: Avoiding data loss with synchronous replication