Allow makeaclitem() to accept multiple privileges

From: "Tharakan, Robins" <tharar(at)amazon(dot)com>
To: "pgsql-hackers(at)postgresql(dot)org" <pgsql-hackers(at)postgresql(dot)org>
Subject: Allow makeaclitem() to accept multiple privileges
Date: 2022-05-27 07:03:52
Message-ID: e5a05dc54ba64408b3dd260171c1abaf@EX13D05UWC001.ant.amazon.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

Presently, makeaclitem() allows only a single privilege in a single call.
This
patch allows it to additionally accept multiple comma-separated privileges.

The attached patch reuses the has_foo_privileges() infrastructure and
besides
a minor change to the function documentation, it also adds 3 regression
tests
that increase the function code-coverage to 100%.

Sample usage:

postgres=# SELECT makeaclitem('postgres'::regrole, 'usr1'::regrole,
'SELECT, INSERT, UPDATE, ALTER SYSTEM', FALSE);
makeaclitem
--------------------
postgres=arwA/usr1
(1 row)

The need for this patch came up during a recent customer experience, where
'pg_init_privs.initprivs' had grantees pointing to non-existent roles. This
is
easy to reproduce [5] and given that this issue was blocking the
customer's planned upgrade, the temporary solution was to UPDATE the
initprivs column. From what I could see, there was a fix for similar
issues [1], although that didn't fix this specific issue [2] and thus
manually
modifying initprivs was required. For this manual update though, if the
proposed feature was available, it would have helped with the UPDATE SQLs.

To elaborate the customer issue, in most rows aclitems[]::TEXT was 2000+
characters long and spanned 30+ missing roles and multiple databases. In
trying
to automate the generation of UPDATE SQLs, I tried to use aclexplode() to
filter-out the missing grantee roles, however re-stitching the remaining
items
back into an aclitems[] array was non-trivial, since makeaclitem() doesn't
yet
accept multiple privileges in a single call. In particular, the
unnest() + string-search approach mentioned in this thread [4] didn't scale
with many missing roles where rolenames were alphanumeric.

See [6] for a contrived example where the updated makeaclitem() can be used
to
regenerate the initprivs column, weeding out privileges related to missing
grantees.

Lastly, while researching, I saw a thread [3] questioning whether
makeaclitem() is useful, and think that if it were to allow multiple
privileges,
it could have helped in the above situation, and thus I'd -1 on dropping the
function.

Reference:
1)
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=47088c599
cc6d6473c7b89ac029060525cf086d8

2)
https://www.postgresql.org/message-id/flat/29913.1573835973%40sss.pgh.pa.us#
90b0192c126ea61266e31dbb864c9b08

3)
https://www.postgresql.org/message-id/flat/48f9156d-3937-cf47-13ee-ac4e90c83
c43%40postgresfriends.org#7f5c830819bc104c222b440689d2028f

4)
https://www.postgresql.org/message-id/flat/1573808483712.96817%40Optiver.com

5) Reproduction to get pg_init_privs.initprivs to point to missing roles.

psql -c "CREATE DATABASE w" postgres
export PGDATABASE=w;
psql -c "CREATE USER usr1 PASSWORD 'usr1' SUPERUSER;"
psql -U usr1 -c "CREATE EXTENSION pg_stat_statements" -c "SELECT * FROM
pg_init_privs WHERE privtype = 'e'" -c "REASSIGN OWNED BY usr1 TO postgres;"
psql -c "DROP USER usr1" -c "SELECT * FROM pg_init_privs WHERE privtype =
'e';"
.
.
.
This would end up with something like this:

r=# select * from pg_init_privs where privtype = 'e';
objoid | classoid | objsubid | privtype |
initprivs
--------+----------+----------+----------+----------------------------------
-------------------------------------
16433 | 1255 | 0 | e | {16425=X/16425}
16441 | 1259 | 0 | e |
{16425=arwdDxt/16425,=r/16425,t=r/postgres}
16452 | 1259 | 0 | e |
{16425=arwdDxt/16425,=r/16425,uw22341=arwdDxt/postgres,t=rw/postgres}
(3 rows)

6) This feature can then be used to generate the UPDATE SQLs to fix the
issue:

r=# BEGIN;
BEGIN
r=*# WITH a AS (SELECT pg_init_privs.* ,(aclexplode(initprivs)).* FROM
pg_init_privs WHERE privtype = 'e')
r-*# SELECT DISTINCT 'UPDATE pg_init_privs SET initprivs = ''{' || (
r(*# WITH x AS (
r(*# SELECT (aclexplode(initprivs)).*
r(*# FROM pg_init_privs
r(*# WHERE objoid = a.objoid AND classoid = a.classoid AND objsubid
= a.objsubid
r(*# )
r(*# ,y AS (
r(*# SELECT makeaclitem(grantee, grantor, string_agg(privilege_type,
','), is_grantable) p
r(*# FROM x
r(*# WHERE grantee IN (SELECT oid FROM pg_roles)
r(*# GROUP BY grantee,grantor,is_grantable
r(*# )
r(*# SELECT string_agg(p::TEXT, ',') FROM y
r(*# ) || '}'' WHERE objoid=' || a.objoid || ' AND classoid=' ||
a.classoid || ' AND objsubid=' || a.objsubid || ';'
r-*# FROM a;
?column?
----------------------------------------------------------------------------
----------------------------------------------------------
UPDATE pg_init_privs SET initprivs = '{t=r/postgres}' WHERE objoid=16441
AND classoid=1259 AND objsubid=0;
UPDATE pg_init_privs SET initprivs =
'{uw22341=arwdDxt/postgres,t=rw/postgres}' WHERE objoid=16452 AND
classoid=1259 AND objsubid=0;

(3 rows)

r=*# \gexec
UPDATE 1
UPDATE 1
r=*# select * from pg_init_privs where privtype = 'e';
objoid | classoid | objsubid | privtype | initprivs
--------+----------+----------+----------+----------------------------------
--------
16433 | 1255 | 0 | e | {16425=X/16425}
16441 | 1259 | 0 | e | {t=r/postgres}
16452 | 1259 | 0 | e |
{uw22341=arwdDxt/postgres,t=rw/postgres}
(3 rows)

(Similarly, it should be possible to generate DELETE SQLs for rows with
*all*
ACL objects pointing to missing roles - for e.g. row 1 above).

-
Robins Tharakan
Amazon Web Services

Attachment Content-Type Size
v1_makeaclitem_multiple_privileges.patch application/octet-stream 6.4 KB

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message shiy.fnst@fujitsu.com 2022-05-27 07:04:18 RE: Handle infinite recursion in logical replication setup
Previous Message Kyotaro Horiguchi 2022-05-27 06:37:35 Re: [BUG] Panic due to incorrect missingContrecPtr after promotion