Thread: [PATCH] Simplify permission checking logic in user.c

[PATCH] Simplify permission checking logic in user.c

From
Paul Martinez
Date:
Hey, hackers,

As part of building Postgres for a managed environment in Google Cloud SQL,
we've made various small changes to permission checks to carefully limit what
customers have access to.

We were making some changes in src/backend/commands/user.c and noticed some
clunky logic related to verifying that the current user has sufficient
permissions to perform an action:


https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/commands/user.c;h=0e6800bf3e4f3f584bbb0b2f6f8ae999f5d94bf1;hb=HEAD#l288

The checks for whether the current user can create a user with the SUPERUSER,
REPLICATION, or BYPASSRLS attributes are chained together using if/else-if,
before finally checking whether the user has CREATEROLE privileges in a
final else. This construction is error-prone, since once one branch is visited,
later ones are skipped, and it implicitly assumes that the permissions needed
for each subsequent action are subsets of the permissions needed for the
previous action. Since each branch requires SUPERUSER this is fine, but
changing one of the checks could inadvertently allow users without the
CREATEROLE permission to create users.

A similar construction is used later in the file in the AlterRole function:


https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/commands/user.c;h=0e6800bf3e4f3f584bbb0b2f6f8ae999f5d94bf1;hb=HEAD#l717

This small patch simply modifies the code to replace the if/else-if chain with
separate if statements, and always checks whether the user has the CREATEROLE
privilege. (The have_createrole_privilege function includes another superuser
check.) Given the current checks in each branch, this code is equivalent, but
easier to modify in the future.

- Paul

Attachment

Re: [PATCH] Simplify permission checking logic in user.c

From
Michael Paquier
Date:
On Tue, Dec 29, 2020 at 02:26:19PM -0600, Paul Martinez wrote:
> The checks for whether the current user can create a user with the SUPERUSER,
> REPLICATION, or BYPASSRLS attributes are chained together using if/else-if,
> before finally checking whether the user has CREATEROLE privileges in a
> final else. This construction is error-prone, since once one branch is visited,
> later ones are skipped, and it implicitly assumes that the permissions needed
> for each subsequent action are subsets of the permissions needed for the
> previous action. Since each branch requires SUPERUSER this is fine, but
> changing one of the checks could inadvertently allow users without the
> CREATEROLE permission to create users.

Hmm.  I agree with your position here.  A careless change could easily
miss that all those if branches have to use a superuser check.

+   if (isreplication)
    {
        if (!superuser())
        ereport(ERROR,
                   (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                    errmsg("must be superuser to create replication users")));
        }
    }
It would be more readable to combine both conditions together, no?
Your change also means that we may call superuser() more times than
necessary, but last_roleid_is_super leverages that, and this code path
is not performance-critical either.

I am adding Stephen in CC, just in case he has some comments to
share.
--
Michael

Attachment

Re: [PATCH] Simplify permission checking logic in user.c

From
Stephen Frost
Date:
Greetings,

* Paul Martinez (paulmtz@google.com) wrote:
> This small patch simply modifies the code to replace the if/else-if chain with
> separate if statements, and always checks whether the user has the CREATEROLE
> privilege. (The have_createrole_privilege function includes another superuser
> check.) Given the current checks in each branch, this code is equivalent, but
> easier to modify in the future.

While I do appreciate that it'd be nice if we made all of the code in
the backend easier for folks to maintain their own patch sets against
core, it'd also mean that we're now being asked to provide some level of
commitment that we aren't going to change these things later because
then we'd break $whomever's patches.  That's certainly not something
that is really reasonable for us to agree to.

I'd strongly suggest that, instead, you consider proposing changes which
would address the actual use cases you have and work with the community
to have those included in core, which would further have the added
property that everyone would then benefit from those improvements.

Thanks,

Stephen

Attachment

Re: [PATCH] Simplify permission checking logic in user.c

From
Stephen Frost
Date:
Greetings,

* Michael Paquier (michael@paquier.xyz) wrote:
> On Tue, Dec 29, 2020 at 02:26:19PM -0600, Paul Martinez wrote:
> > The checks for whether the current user can create a user with the SUPERUSER,
> > REPLICATION, or BYPASSRLS attributes are chained together using if/else-if,
> > before finally checking whether the user has CREATEROLE privileges in a
> > final else. This construction is error-prone, since once one branch is visited,
> > later ones are skipped, and it implicitly assumes that the permissions needed
> > for each subsequent action are subsets of the permissions needed for the
> > previous action. Since each branch requires SUPERUSER this is fine, but
> > changing one of the checks could inadvertently allow users without the
> > CREATEROLE permission to create users.
>
> Hmm.  I agree with your position here.  A careless change could easily
> miss that all those if branches have to use a superuser check.

That really strikes me as pretty darn unlikely to happen, it's not like
this code is really spread out across very many lines or that it's hard
to see the pretty clear pattern.

In thinking about these role attributes and the restrictions we have
about what attributes don't require superuser to set and what do, I do
feel like we're probably missing a bet regarding replication and
bypassrls..  In other words, I suspect people would be happier if we
provided a way for non-superusers a way to create replication roles and
bypassrls roles.  Exactly how we do that is a bit tricky to figure out
but that's certainly a much more productive direction to be going in.

Thanks,

Stephen

Attachment

Re: [PATCH] Simplify permission checking logic in user.c

From
Andrey Borodin
Date:

> 30 дек. 2020 г., в 20:26, Stephen Frost <sfrost@snowman.net> написал(а):
>
> I'd strongly suggest that, instead, you consider proposing changes which
> would address the actual use cases you have and work with the community
> to have those included in core, which would further have the added
> property that everyone would then benefit from those improvements.
+1. Last time we asked to change something in privileges[0], we got a feedback pointing to possible vulnerability.
We fixed it in our services and reported to, AFAIR, RDS and Aiven (with PoC exploits).

I think that sharing "various small changes to permission checks" is a really good idea.

> 30 дек. 2020 г., в 20:39, Stephen Frost <sfrost@snowman.net> написал(а):
> In other words, I suspect people would be happier if we
> provided a way for non-superusers a way to create replication roles and
> bypassrls roles.
+1 again. I hope we will return to the topic soon.

Best regards, Andrey Borodin.

[0] https://www.postgresql.org/message-id/flat/1269681541151271%40myt5-68ad52a76c91.qloud-c.yandex.net


Re: [PATCH] Simplify permission checking logic in user.c

From
Paul Martinez
Date:
On Wed, Dec 30, 2020 at 12:28 PM Andrey Borodin <x4mmm@yandex-team.ru> wrote:
> I think that sharing "various small changes to permission checks" is a really good idea.
>
> > 30 дек. 2020 г., в 20:39, Stephen Frost <sfrost@snowman.net> написал(а):
> > In other words, I suspect people would be happier if we
> > provided a way for non-superusers a way to create replication roles and
> > bypassrls roles.
> +1 again. I hope we will return to the topic soon.

On Wed, Dec 30, 2020 at 9:26 AM Stephen Frost <sfrost@snowman.net> wrote:
> While I do appreciate that it'd be nice if we made all of the code in
> the backend easier for folks to maintain their own patch sets against
> core, it'd also mean that we're now being asked to provide some level of
> commitment that we aren't going to change these things later because
> then we'd break $whomever's patches.  That's certainly not something
> that is really reasonable for us to agree to.
>
> I'd strongly suggest that, instead, you consider proposing changes which
> would address the actual use cases you have and work with the community
> to have those included in core, which would further have the added
> property that everyone would then benefit from those improvements.

On Wed, Dec 30, 2020 at 9:39 AM Stephen Frost <sfrost@snowman.net> wrote:
>
> That really strikes me as pretty darn unlikely to happen, it's not like
> this code is really spread out across very many lines or that it's hard
> to see the pretty clear pattern.
>
> In thinking about these role attributes and the restrictions we have
> about what attributes don't require superuser to set and what do, I do
> feel like we're probably missing a bet regarding replication and
> bypassrls..  In other words, I suspect people would be happier if we
> provided a way for non-superusers a way to create replication roles and
> bypassrls roles.  Exactly how we do that is a bit tricky to figure out
> but that's certainly a much more productive direction to be going in.

Thanks everyone for taking a look!

You've identified exactly the problem we're running into -- we want to
allow customers, who aren't superusers, to create replication roles.

In Google Cloud SQL we create a role for customers, cloudsqlsuperuser,
which, confusingly, is not a SUPERUSER, but does have some extra
permissions. We've modified a lot of "if (!superuser())" checks to
"if (!superuser() && !cloudsqlsuperuser())".

That was actually what I initially tried to do when modifying this bit of
code:

  if (issuperuser)
    if (!superuser()) ereport(...);
  elsif (isreplication)
-   if (!superuser()) ereport(...);
+   if (!superuser() && !cloudsqlsuperuser()) ereport(...);
  elsif (bypassrls)
    if (!superuser()) ereport(...);
  else
    if (!have_createrole_privilege()) ereport()

But this inadvertently allowed cloudsqlsuperuser to _also_ create users
with the BYPASSRLS attribute by creating a user with both REPLICATION
and BYPASSRLS, which I only realized when a couple of tests failed.
Properly modifying the required permissions required separating the
if/else branches, which led to this patch.

From my understanding, AWS RDS Postgres takes a slightly different approach
and allows the REPLICATION attribute to be inherited through membership in
a special rds_replication role.



We haven't seriously considered what some sort of general functionality
would look like to support our managed Postgres use-cases, but here's a
rough sketch of what we're trying to accomplish with roles and permissions:

- We, ideally, want to give our customers access to as much of Postgres'
  functionality as possible, including SUPERUSER capabilities

- But we do not want customers to have access to the underlying VM or
  filesystem

- There are certain parts of the system (i.e., certain databases, tables,
  individual rows in catalog tables, etc.) that we want to manage and we
  can't allow customers to modify these at all. Examples include:
  - Our own SUPERUSER role that we use to connect to the cluster; customers
    shouldn't be able to modify this role at all
  - Replication slots used for managed replication shouldn't be able to be
    modified by customers (even if they have the REPLICATION attribute)

- We want to prevent customers from depending on any data we choose to store
  in other database, as that limits our flexibility to make future changes
  - Notably this means we only can support logical replication, and not
    physical replication.

I suppose this could be roughly summarized as "90% of a superuser, but also
still obeys SQL privileges for objects created by someone else".

We would definitely be happy to explore what sort of functionality like this
would be useful for others!



Re: [PATCH] Simplify permission checking logic in user.c

From
Stephen Frost
Date:
Greetings,

* Paul Martinez (paulmtz@google.com) wrote:
> You've identified exactly the problem we're running into -- we want to
> allow customers, who aren't superusers, to create replication roles.

This is also where it's probably useful to think about what the impact
of that is- after all, since it seems you're considering this for the
purposes of logical replication, it'd make sense to draw your attention
to this:

https://www.postgresql.org/docs/10/logical-replication-security.html

which talks about the rather serious consequences, from a security
perspective, of allowing non-superusers access to logical replication
and modifying publication and subscription sets and tables.  Perhaps
these are things you've already addressed, but we'd need to address them
in core to have such a capability make sense.

> We haven't seriously considered what some sort of general functionality
> would look like to support our managed Postgres use-cases, but here's a
> rough sketch of what we're trying to accomplish with roles and permissions:
>
> - We, ideally, want to give our customers access to as much of Postgres'
>   functionality as possible, including SUPERUSER capabilities
>
> - But we do not want customers to have access to the underlying VM or
>   filesystem

Certainly, the above two things are in pretty direct conflict, as you've
discovered. :)

That said, we've started working in this direction with the initial (or
default) role system, as a way to express capabilities which can then be
inherited or GRANT'd by roles with appropriate rights to other roles.
In general, it seems like that's a better approach and that it'd make
sense to move away from the role attributes.

> - There are certain parts of the system (i.e., certain databases, tables,
>   individual rows in catalog tables, etc.) that we want to manage and we
>   can't allow customers to modify these at all. Examples include:
>   - Our own SUPERUSER role that we use to connect to the cluster; customers
>     shouldn't be able to modify this role at all

Roles, in general, can't modify other roles (except with CREATEROLE,
which is more an argument to get rid of it and not use it than anything
else, imv).

>   - Replication slots used for managed replication shouldn't be able to be
>     modified by customers (even if they have the REPLICATION attribute)

That's an interesting example and one which probably means we need to
have some kind of ownership/privilege model added to replication slots.

> - We want to prevent customers from depending on any data we choose to store
>   in other database, as that limits our flexibility to make future changes
>   - Notably this means we only can support logical replication, and not
>     physical replication.
>

Being able to give users the ability to use logical replication for a
subset of the system is certainly a capability that I think we'd like to
have.

> I suppose this could be roughly summarized as "90% of a superuser, but also
> still obeys SQL privileges for objects created by someone else".

I don't really think that it makes sense to think of it that way-
superuser is superuser, everything is "user with certain privileges
granted to it" and we should be thinking of it that way.

Thanks,

Stephen

Attachment

Re: [PATCH] Simplify permission checking logic in user.c

From
Andrey Borodin
Date:

> 31 дек. 2020 г., в 00:37, Paul Martinez <paulmtz@google.com> написал(а):
>
> In Google Cloud SQL we create a role for customers, cloudsqlsuperuser,
> which, confusingly, is not a SUPERUSER, but does have some extra
> permissions. We've modified a lot of "if (!superuser())" checks to
> "if (!superuser() && !cloudsqlsuperuser())".

You use cloudsqlsuperuser.
RDS has rds_superuser.
Aiven has their aiven_extras extension.
Yandex Cloud has mdb_admin and mdb_replication.

Some of us, probably, could do something useful for the project instead of rebasing those patches and extensions.
Let's start to work together with community. Let's address issues that thread[0] faced and restart it.

Happy New Year :)

Best regards, Andrey Borodin.

[0]
https://www.postgresql.org/message-id/flat/f41d93b6-69ba-fa05-c91a-045bafa5f832%402ndquadrant.com#636c800abc6f464c388db7f532a389ba