Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 6550b90

Browse files
committed
Code review for row security.
Buildfarm member tick identified an issue where the policies in the relcache for a relation were were being replaced underneath a running query, leading to segfaults while processing the policies to be added to a query. Similar to how TupleDesc RuleLocks are handled, add in a equalRSDesc() function to check if the policies have actually changed and, if not, swap back the rsdesc field (using the original instead of the temporairly built one; the whole structure is swapped and then specific fields swapped back). This now passes a CLOBBER_CACHE_ALWAYS for me and should resolve the buildfarm error. In addition to addressing this, add a new chapter in Data Definition under Privileges which explains row security and provides examples of its usage, change \d to always list policies (even if row security is disabled- but note that it is disabled, or enabled with no policies), rework check_role_for_policy (it really didn't need the entire policy, but it did need to be using has_privs_of_role()), and change the field in pg_class to relrowsecurity from relhasrowsecurity, based on Heikki's suggestion. Also from Heikki, only issue SET ROW_SECURITY in pg_restore when talking to a 9.5+ server, list Bypass RLS in \du, and document --enable-row-security options for pg_dump and pg_restore. Lastly, fix a number of minor whitespace and typo issues from Heikki, Dimitri, add a missing #include, per Peter E, fix a few minor variable-assigned-but-not-used and resource leak issues from Coverity and add tab completion for role attribute bypassrls as well.
1 parent 3f6f926 commit 6550b90

24 files changed

+439
-122
lines changed

doc/src/sgml/catalogs.sgml

+6-5
Original file line numberDiff line numberDiff line change
@@ -1941,8 +1941,9 @@
19411941
</row>
19421942

19431943
<row>
1944-
<entry><structfield>relhasrowsecurity</structfield></entry>
1944+
<entry><structfield>relrowsecurity</structfield></entry>
19451945
<entry><type>bool</type></entry>
1946+
<entry></entry>
19461947
<entry>
19471948
True if table has row-security enabled; see
19481949
<link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
@@ -5415,7 +5416,7 @@
54155416

54165417
<note>
54175418
<para>
5418-
<literal>pg_class.relhasrowsecurity</literal>
5419+
<literal>pg_class.relrowsecurity</literal>
54195420
True if the table has row-security enabled.
54205421
Must be true if the table has a row-security policy in this catalog.
54215422
</para>
@@ -9228,10 +9229,10 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
92289229
<entry>True if table has (or once had) triggers</entry>
92299230
</row>
92309231
<row>
9231-
<entry><structfield>hasrowsecurity</structfield></entry>
9232+
<entry><structfield>rowsecurity</structfield></entry>
92329233
<entry><type>boolean</type></entry>
9233-
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhasrowsecurity</literal></entry>
9234-
<entry>True if table has row security enabled</entry>
9234+
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relrowsecurity</literal></entry>
9235+
<entry>True if row security is enabled on the table</entry>
92359236
</row>
92369237
</tbody>
92379238
</tgroup>

doc/src/sgml/config.sgml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5457,9 +5457,9 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
54575457

54585458
<para>
54595459
The allowed values of <varname>row_security</> are
5460-
<literal>on</> (apply normally- not to superuser or table owner),
5460+
<literal>on</> (apply normally - not to superuser or table owner),
54615461
<literal>off</> (fail if row security would be applied), and
5462-
<literal>force</> (apply always- even to superuser and table owner).
5462+
<literal>force</> (apply always - even to superuser and table owner).
54635463
</para>
54645464

54655465
<para>

doc/src/sgml/ddl.sgml

+168
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,174 @@ REVOKE ALL ON accounts FROM PUBLIC;
15081508
</para>
15091509
</sect1>
15101510

1511+
<sect1 id="ddl-rowsecurity">
1512+
<title>Row Security Policies</title>
1513+
1514+
<indexterm zone="ddl-rowsecurity">
1515+
<primary>rowsecurity</primary>
1516+
</indexterm>
1517+
1518+
<indexterm zone="ddl-rowsecurity">
1519+
<primary>rls</primary>
1520+
</indexterm>
1521+
1522+
<indexterm>
1523+
<primary>policies</primary>
1524+
<see>policy</see>
1525+
</indexterm>
1526+
1527+
<indexterm zone="ddl-rowsecurity">
1528+
<primary>POLICY</primary>
1529+
</indexterm>
1530+
1531+
<para>
1532+
In addition to the <xref linkend="ddl-priv"> system available through
1533+
<xref linkend="sql-grant">, tables can have row security policies
1534+
which limit the rows returned for normal queries and rows which can
1535+
be added through data modification commands. By default, tables do
1536+
not have any policies and all rows are visible and able to be added,
1537+
subject to the regular <xref linkend="ddl-priv"> system. This is
1538+
also known to as Row Level Security.
1539+
</para>
1540+
1541+
<para>
1542+
When row security is enabled on a table with
1543+
<xref linkend="sql-altertable">, all normal access to the table
1544+
(excluding the owner) for selecting rows or adding rows must be through
1545+
a policy. If no policy exists for the table, a default-deny policy is
1546+
used and no rows are visible or can be added. Privileges which operate
1547+
at the whole table level such as <literal>TRUNCATE</>, and
1548+
<literal>REFERENCES</> are not subject to row security.
1549+
</para>
1550+
1551+
<para>
1552+
Row security policies can be specific to commands, or to roles, or to
1553+
both. The commands available are <literal>SELECT</>, <literal>INSERT</>,
1554+
<literal>UPDATE</>, and <literal>DELETE</>. Multiple roles can be
1555+
assigned to a given policy and normal role membership and inheiritance
1556+
rules apply.
1557+
</para>
1558+
1559+
<para>
1560+
To specify which rows are visible and what rows can be added to the
1561+
table with row security, an expression is required which returns a
1562+
boolean result. This expression will be evaluated for each row prior
1563+
to other conditionals or functions which are part of the query. The
1564+
one exception to this rule are <literal>leakproof</literal> functions,
1565+
which are guaranteed to not leak information. Two expressions may be
1566+
specified to provide independent control over the rows which are
1567+
visible and the rows which are allowed to be added. The expression
1568+
is run as part of the query and with the privileges of the user
1569+
running the query, however, security definer functions can be used in
1570+
the expression.
1571+
</para>
1572+
1573+
<para>
1574+
Enabling and disabling row security, as well as adding policies to a
1575+
table, is always the privilege of the owner only.
1576+
</para>
1577+
1578+
<para>
1579+
Policies are created using the <xref linkend="sql-createpolicy">
1580+
command, altered using the <xref linkend="sql-alterpolicy"> command,
1581+
and dropped using the <xref linkend="sql-droppolicy"> command. To
1582+
enable and disable row security for a given table, use the
1583+
<xref linkend="sql-altertable"> command.
1584+
</para>
1585+
1586+
<para>
1587+
The table owners and superusers bypass the row security system when
1588+
querying a table, by default. Row security can be enabled for
1589+
superusers and table owners by setting
1590+
<xref linkend="guc-row-security"> to <literal>force</literal>. Any
1591+
user can request that row security be bypassed by setting
1592+
<xref linkend="guc-row-security"> to <literal>off</literal>. If
1593+
the user does not have privileges to bypass row security when
1594+
querying a given table then an error will be returned instead. Other
1595+
users can be granted the ability to bypass the row security system
1596+
with the <literal>BYPASSRLS</literal> role attribute. This
1597+
attribute can only be set by a superuser.
1598+
</para>
1599+
1600+
<para>
1601+
Each policy has a name and multiple policies can be defined for a
1602+
table. As policies are table-specific, each policy for a table must
1603+
have a unique name. Different tables may have policies with the
1604+
same name.
1605+
</para>
1606+
1607+
<para>
1608+
When multiple policies apply to a given query, they are combined using
1609+
<literal>OR</literal>, similar to how a given role has the privileges
1610+
of all roles which they are a member of.
1611+
</para>
1612+
1613+
<para>
1614+
Referential integrity checks, such as unique or primary key constraints
1615+
and foreign key references, will bypass row security to ensure that
1616+
data integrity is maintained. Care must be taken when developing
1617+
schemas and row level policies to avoid a "covert channel" leak of
1618+
information through these referntial integrity checks.
1619+
</para>
1620+
1621+
<para>
1622+
To enable row security for a table,
1623+
the <command>ALTER TABLE</command> is used. For example, to enable
1624+
row level security for the table accounts, use:
1625+
</para>
1626+
1627+
<programlisting>
1628+
-- Create the table first
1629+
CREATE TABLE accounts (manager text, company text, contact_email text);
1630+
ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
1631+
</programlisting>
1632+
1633+
<para>
1634+
To create a policy on the account relation to allow the managers role
1635+
to view the rows of their accounts, the <command>CREATE POLICY</command>
1636+
command can be used:
1637+
</para>
1638+
1639+
<programlisting>
1640+
CREATE POLICY account_managers ON accounts TO managers
1641+
USING (manager = current_user);
1642+
</programlisting>
1643+
1644+
<para>
1645+
If no role is specified, or the special <quote>user</quote> name
1646+
<literal>PUBLIC</literal> is used, then the policy applies to all
1647+
users on the system. To allow all users to view their own row in
1648+
a user table, a simple policy can be used:
1649+
</para>
1650+
1651+
<programlisting>
1652+
CREATE POLICY user_policy ON users
1653+
USING (user = current_user);
1654+
</programlisting>
1655+
1656+
<para>
1657+
To use a different policy for rows which are being added to the
1658+
table from those rows which are visible, the WITH CHECK clause
1659+
can be used. This would allow all users to view all rows in the
1660+
users table, but only modify their own:
1661+
</para>
1662+
1663+
<programlisting>
1664+
CREATE POLICY user_policy ON users
1665+
USING (true)
1666+
WITH CHECK (user = current_user);
1667+
</programlisting>
1668+
1669+
<para>
1670+
Row security can be disabled with the <command>ALTER TABLE</command>
1671+
also. Note that disabling row security does not remove the
1672+
policies which are defined on the table, they are simply ignored
1673+
and all rows are visible and able to be added, subject to the
1674+
normal privileges system.
1675+
</para>
1676+
1677+
</sect1>
1678+
15111679
<sect1 id="ddl-schemas">
15121680
<title>Schemas</title>
15131681

doc/src/sgml/ref/alter_table.sgml

+1-1
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
429429
These forms control the application of row security policies belonging
430430
to the table. If enabled and no policies exist for the table, then a
431431
default-deny policy is applied. Note that policies can exist for a table
432-
even if row level security is disabled- in this case, the policies will
432+
even if row level security is disabled - in this case, the policies will
433433
NOT be applied and the policies will be ignored.
434434
See also
435435
<xref linkend="SQL-CREATEPOLICY">.

doc/src/sgml/ref/create_policy.sgml

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
240240
</varlistentry>
241241

242242
<varlistentry id="SQL-CREATEPOLICY-UPDATE">
243-
<term><literal>DELETE</></term>
243+
<term><literal>UPDATE</></term>
244244
<listitem>
245245
<para>
246246
Using <literal>UPDATE</literal> for a policy means that it will apply

doc/src/sgml/ref/pg_dump.sgml

+17
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,23 @@ PostgreSQL documentation
687687
</listitem>
688688
</varlistentry>
689689

690+
<varlistentry>
691+
<term><option>--enable-row-security</></term>
692+
<listitem>
693+
<para>
694+
This option is relevant only when dumping the contents of a table
695+
which has row security. By default, pg_dump will set
696+
<literal>ROW_SECURITY</literal> to <literal>OFF</literal>, to ensure
697+
that all data is dumped from the table. If the user does not have
698+
sufficient privileges to bypass row security, then an error is thrown.
699+
This parameter instructs <application>pg_dump</application> to set
700+
row_security to 'ON' instead, allowing the user to dump the contents
701+
of the table which they have access to.
702+
</para>
703+
704+
</listitem>
705+
</varlistentry>
706+
690707
<varlistentry>
691708
<term><option>--exclude-table-data=<replaceable class="parameter">table</replaceable></option></term>
692709
<listitem>

doc/src/sgml/ref/pg_restore.sgml

+23
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,29 @@
490490
</listitem>
491491
</varlistentry>
492492

493+
<varlistentry>
494+
<term><option>--enable-row-security</></term> <listitem>
495+
<para>
496+
This option is relevant only when restoring the contents of a table
497+
which has row security. By default, pg_restore will set
498+
<literal>ROW_SECURITY</literal> to <literal>OFF</literal>, to ensure
499+
that all data is restored in to the table. If the user does not have
500+
sufficient privileges to bypass row security, then an error is thrown.
501+
This parameter instructs <application>pg_restore</application> to set
502+
row_security to 'ON' instead, allowing the user to attempt to restore
503+
the contents of the table with row security enabled. This may still
504+
fail if the user does not have the right to insert the rows from the
505+
dump into the table.
506+
</para>
507+
508+
<para>
509+
Note that this option currently also requires the dump be in INSERT
510+
format as COPY TO does not support row security.
511+
</para>
512+
513+
</listitem>
514+
</varlistentry>
515+
493516
<varlistentry>
494517
<term><option>--if-exists</option></term>
495518
<listitem>

src/backend/catalog/heap.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ InsertPgClassTuple(Relation pg_class_desc,
799799
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
800800
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
801801
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
802-
values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
802+
values[Anum_pg_class_relrowsecurity - 1] = BoolGetDatum(rd_rel->relrowsecurity);
803803
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
804804
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
805805
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);

src/backend/catalog/system_views.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ CREATE VIEW pg_tables AS
119119
C.relhasindex AS hasindexes,
120120
C.relhasrules AS hasrules,
121121
C.relhastriggers AS hastriggers,
122-
C.relhasrowsecurity AS hasrowsecurity
122+
C.relrowsecurity AS rowsecurity
123123
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
124124
LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
125125
WHERE C.relkind = 'r';

src/backend/commands/policy.c

+8-9
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ parse_row_security_command(const char *cmd_name)
108108
char cmd;
109109

110110
if (!cmd_name)
111-
elog(ERROR, "Unregonized command.");
111+
elog(ERROR, "unregonized command");
112112

113113
if (strcmp(cmd_name, "all") == 0)
114114
cmd = 0;
@@ -121,8 +121,7 @@ parse_row_security_command(const char *cmd_name)
121121
else if (strcmp(cmd_name, "delete") == 0)
122122
cmd = ACL_DELETE_CHR;
123123
else
124-
elog(ERROR, "Unregonized command.");
125-
/* error unrecognized command */
124+
elog(ERROR, "unregonized command");
126125

127126
return cmd;
128127
}
@@ -422,8 +421,8 @@ RemovePolicyById(Oid policy_id)
422421
heap_close(rel, AccessExclusiveLock);
423422

424423
/*
425-
* Note that, unlike some of the other flags in pg_class, relhasrowsecurity
426-
* is not just an indication of if policies exist. When relhasrowsecurity
424+
* Note that, unlike some of the other flags in pg_class, relrowsecurity
425+
* is not just an indication of if policies exist. When relrowsecurity
427426
* is set (which can be done directly by the user or indirectly by creating
428427
* a policy on the table), then all access to the relation must be through
429428
* a policy. If no policy is defined for the relation then a default-deny
@@ -484,7 +483,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
484483
if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL)
485484
ereport(ERROR,
486485
(errcode(ERRCODE_SYNTAX_ERROR),
487-
errmsg("Only WITH CHECK expression allowed for INSERT")));
486+
errmsg("only WITH CHECK expression allowed for INSERT")));
488487

489488

490489
/* Collect role ids */
@@ -731,7 +730,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
731730
if (!HeapTupleIsValid(rsec_tuple))
732731
ereport(ERROR,
733732
(errcode(ERRCODE_UNDEFINED_OBJECT),
734-
errmsg("policy '%s' for does not exist on table %s",
733+
errmsg("policy \"%s\" on table \"%s\" does not exist",
735734
stmt->policy_name,
736735
RelationGetRelationName(target_table))));
737736

@@ -850,7 +849,7 @@ rename_policy(RenameStmt *stmt)
850849

851850
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
852851

853-
/* First pass- check for conflict */
852+
/* First pass -- check for conflict */
854853

855854
/* Add key - row security relation id. */
856855
ScanKeyInit(&skey[0],
@@ -868,7 +867,7 @@ rename_policy(RenameStmt *stmt)
868867
RowSecurityRelidPolnameIndexId, true, NULL, 2,
869868
skey);
870869

871-
if (HeapTupleIsValid(rsec_tuple = systable_getnext(sscan)))
870+
if (HeapTupleIsValid(systable_getnext(sscan)))
872871
ereport(ERROR,
873872
(errcode(ERRCODE_DUPLICATE_OBJECT),
874873
errmsg("row-policy \"%s\" for table \"%s\" already exists",

src/backend/commands/tablecmds.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -10647,7 +10647,7 @@ ATExecEnableRowSecurity(Relation rel)
1064710647
if (!HeapTupleIsValid(tuple))
1064810648
elog(ERROR, "cache lookup failed for relation %u", relid);
1064910649

10650-
((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = true;
10650+
((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = true;
1065110651
simple_heap_update(pg_class, &tuple->t_self, tuple);
1065210652

1065310653
/* keep catalog indexes current */
@@ -10674,7 +10674,7 @@ ATExecDisableRowSecurity(Relation rel)
1067410674
if (!HeapTupleIsValid(tuple))
1067510675
elog(ERROR, "cache lookup failed for relation %u", relid);
1067610676

10677-
((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = false;
10677+
((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = false;
1067810678
simple_heap_update(pg_class, &tuple->t_self, tuple);
1067910679

1068010680
/* keep catalog indexes current */

0 commit comments

Comments
 (0)