summaryrefslogtreecommitdiffstats
path: root/doc/src/sgml/html/ddl-rowsecurity.html
diff options
context:
space:
mode:
Diffstat (limited to 'doc/src/sgml/html/ddl-rowsecurity.html')
-rw-r--r--doc/src/sgml/html/ddl-rowsecurity.html382
1 files changed, 382 insertions, 0 deletions
diff --git a/doc/src/sgml/html/ddl-rowsecurity.html b/doc/src/sgml/html/ddl-rowsecurity.html
new file mode 100644
index 0000000..ec0be3b
--- /dev/null
+++ b/doc/src/sgml/html/ddl-rowsecurity.html
@@ -0,0 +1,382 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>5.8. Row Security Policies</title><link rel="stylesheet" type="text/css" href="stylesheet.css" /><link rev="made" href="pgsql-docs@lists.postgresql.org" /><meta name="generator" content="DocBook XSL Stylesheets Vsnapshot" /><link rel="prev" href="ddl-priv.html" title="5.7. Privileges" /><link rel="next" href="ddl-schemas.html" title="5.9. Schemas" /></head><body id="docContent" class="container-fluid col-10"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="5" align="center">5.8. Row Security Policies</th></tr><tr><td width="10%" align="left"><a accesskey="p" href="ddl-priv.html" title="5.7. Privileges">Prev</a> </td><td width="10%" align="left"><a accesskey="u" href="ddl.html" title="Chapter 5. Data Definition">Up</a></td><th width="60%" align="center">Chapter 5. Data Definition</th><td width="10%" align="right"><a accesskey="h" href="index.html" title="PostgreSQL 16.2 Documentation">Home</a></td><td width="10%" align="right"> <a accesskey="n" href="ddl-schemas.html" title="5.9. Schemas">Next</a></td></tr></table><hr /></div><div class="sect1" id="DDL-ROWSECURITY"><div class="titlepage"><div><div><h2 class="title" style="clear: both">5.8. Row Security Policies <a href="#DDL-ROWSECURITY" class="id_link">#</a></h2></div></div></div><a id="id-1.5.4.10.2" class="indexterm"></a><a id="id-1.5.4.10.3" class="indexterm"></a><p>
+ In addition to the SQL-standard <a class="link" href="ddl-priv.html" title="5.7. Privileges">privilege
+ system</a> available through <a class="xref" href="sql-grant.html" title="GRANT"><span class="refentrytitle">GRANT</span></a>,
+ tables can have <em class="firstterm">row security policies</em> that restrict,
+ on a per-user basis, which rows can be returned by normal queries
+ or inserted, updated, or deleted by data modification commands.
+ This feature is also known as <em class="firstterm">Row-Level Security</em>.
+ By default, tables do not have any policies, so that if a user has
+ access privileges to a table according to the SQL privilege system,
+ all rows within it are equally available for querying or updating.
+ </p><p>
+ When row security is enabled on a table (with
+ <a class="link" href="sql-altertable.html" title="ALTER TABLE">ALTER TABLE ... ENABLE ROW LEVEL
+ SECURITY</a>), all normal access to the table for selecting rows or
+ modifying rows must be allowed by a row security policy. (However, the
+ table's owner is typically not subject to row security policies.) If no
+ policy exists for the table, a default-deny policy is used, meaning that
+ no rows are visible or can be modified. Operations that apply to the
+ whole table, such as <code class="command">TRUNCATE</code> and <code class="literal">REFERENCES</code>,
+ are not subject to row security.
+ </p><p>
+ Row security policies can be specific to commands, or to roles, or to
+ both. A policy can be specified to apply to <code class="literal">ALL</code>
+ commands, or to <code class="literal">SELECT</code>, <code class="literal">INSERT</code>, <code class="literal">UPDATE</code>,
+ or <code class="literal">DELETE</code>. Multiple roles can be assigned to a given
+ policy, and normal role membership and inheritance rules apply.
+ </p><p>
+ To specify which rows are visible or modifiable according to a policy,
+ an expression is required that returns a Boolean result. This
+ expression will be evaluated for each row prior to any conditions or
+ functions coming from the user's query. (The only exceptions to this
+ rule are <code class="literal">leakproof</code> functions, which are guaranteed to
+ not leak information; the optimizer may choose to apply such functions
+ ahead of the row-security check.) Rows for which the expression does
+ not return <code class="literal">true</code> will not be processed. Separate expressions
+ may be specified to provide independent control over the rows which are
+ visible and the rows which are allowed to be modified. Policy
+ expressions are run as part of the query and with the privileges of the
+ user running the query, although security-definer functions can be used
+ to access data not available to the calling user.
+ </p><p>
+ Superusers and roles with the <code class="literal">BYPASSRLS</code> attribute always
+ bypass the row security system when accessing a table. Table owners
+ normally bypass row security as well, though a table owner can choose to
+ be subject to row security with <a class="link" href="sql-altertable.html" title="ALTER TABLE">ALTER
+ TABLE ... FORCE ROW LEVEL SECURITY</a>.
+ </p><p>
+ Enabling and disabling row security, as well as adding policies to a
+ table, is always the privilege of the table owner only.
+ </p><p>
+ Policies are created using the <a class="xref" href="sql-createpolicy.html" title="CREATE POLICY"><span class="refentrytitle">CREATE POLICY</span></a>
+ command, altered using the <a class="xref" href="sql-alterpolicy.html" title="ALTER POLICY"><span class="refentrytitle">ALTER POLICY</span></a> command,
+ and dropped using the <a class="xref" href="sql-droppolicy.html" title="DROP POLICY"><span class="refentrytitle">DROP POLICY</span></a> command. To
+ enable and disable row security for a given table, use the
+ <a class="xref" href="sql-altertable.html" title="ALTER TABLE"><span class="refentrytitle">ALTER TABLE</span></a> command.
+ </p><p>
+ Each policy has a name and multiple policies can be defined for a
+ table. As policies are table-specific, each policy for a table must
+ have a unique name. Different tables may have policies with the
+ same name.
+ </p><p>
+ When multiple policies apply to a given query, they are combined using
+ either <code class="literal">OR</code> (for permissive policies, which are the
+ default) or using <code class="literal">AND</code> (for restrictive policies).
+ This is similar to the rule that a given role has the privileges
+ of all roles that they are a member of. Permissive vs. restrictive
+ policies are discussed further below.
+ </p><p>
+ As a simple example, here is how to create a policy on
+ the <code class="literal">account</code> relation to allow only members of
+ the <code class="literal">managers</code> role to access rows, and only rows of their
+ accounts:
+ </p><pre class="programlisting">
+CREATE TABLE accounts (manager text, company text, contact_email text);
+
+ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
+
+CREATE POLICY account_managers ON accounts TO managers
+ USING (manager = current_user);
+</pre><p>
+ The policy above implicitly provides a <code class="literal">WITH CHECK</code>
+ clause identical to its <code class="literal">USING</code> clause, so that the
+ constraint applies both to rows selected by a command (so a manager
+ cannot <code class="command">SELECT</code>, <code class="command">UPDATE</code>,
+ or <code class="command">DELETE</code> existing rows belonging to a different
+ manager) and to rows modified by a command (so rows belonging to a
+ different manager cannot be created via <code class="command">INSERT</code>
+ or <code class="command">UPDATE</code>).
+ </p><p>
+ If no role is specified, or the special user name
+ <code class="literal">PUBLIC</code> is used, then the policy applies to all
+ users on the system. To allow all users to access only their own row in
+ a <code class="literal">users</code> table, a simple policy can be used:
+ </p><pre class="programlisting">
+CREATE POLICY user_policy ON users
+ USING (user_name = current_user);
+</pre><p>
+ This works similarly to the previous example.
+ </p><p>
+ To use a different policy for rows that are being added to the table
+ compared to those rows that are visible, multiple policies can be
+ combined. This pair of policies would allow all users to view all rows
+ in the <code class="literal">users</code> table, but only modify their own:
+ </p><pre class="programlisting">
+CREATE POLICY user_sel_policy ON users
+ FOR SELECT
+ USING (true);
+CREATE POLICY user_mod_policy ON users
+ USING (user_name = current_user);
+</pre><p>
+ In a <code class="command">SELECT</code> command, these two policies are combined
+ using <code class="literal">OR</code>, with the net effect being that all rows
+ can be selected. In other command types, only the second policy applies,
+ so that the effects are the same as before.
+ </p><p>
+ Row security can also be disabled with the <code class="command">ALTER TABLE</code>
+ command. Disabling row security does not remove any policies that are
+ defined on the table; they are simply ignored. Then all rows in the
+ table are visible and modifiable, subject to the standard SQL privileges
+ system.
+ </p><p>
+ Below is a larger example of how this feature can be used in production
+ environments. The table <code class="literal">passwd</code> emulates a Unix password
+ file:
+ </p><pre class="programlisting">
+-- Simple passwd-file based example
+CREATE TABLE passwd (
+ user_name text UNIQUE NOT NULL,
+ pwhash text,
+ uid int PRIMARY KEY,
+ gid int NOT NULL,
+ real_name text NOT NULL,
+ home_phone text,
+ extra_info text,
+ home_dir text NOT NULL,
+ shell text NOT NULL
+);
+
+CREATE ROLE admin; -- Administrator
+CREATE ROLE bob; -- Normal user
+CREATE ROLE alice; -- Normal user
+
+-- Populate the table
+INSERT INTO passwd VALUES
+ ('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/dash');
+INSERT INTO passwd VALUES
+ ('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/zsh');
+INSERT INTO passwd VALUES
+ ('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/bin/zsh');
+
+-- Be sure to enable row-level security on the table
+ALTER TABLE passwd ENABLE ROW LEVEL SECURITY;
+
+-- Create policies
+-- Administrator can see all rows and add any rows
+CREATE POLICY admin_all ON passwd TO admin USING (true) WITH CHECK (true);
+-- Normal users can view all rows
+CREATE POLICY all_view ON passwd FOR SELECT USING (true);
+-- Normal users can update their own records, but
+-- limit which shells a normal user is allowed to set
+CREATE POLICY user_mod ON passwd FOR UPDATE
+ USING (current_user = user_name)
+ WITH CHECK (
+ current_user = user_name AND
+ shell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/tcsh')
+ );
+
+-- Allow admin all normal rights
+GRANT SELECT, INSERT, UPDATE, DELETE ON passwd TO admin;
+-- Users only get select access on public columns
+GRANT SELECT
+ (user_name, uid, gid, real_name, home_phone, extra_info, home_dir, shell)
+ ON passwd TO public;
+-- Allow users to update certain columns
+GRANT UPDATE
+ (pwhash, real_name, home_phone, extra_info, shell)
+ ON passwd TO public;
+</pre><p>
+ As with any security settings, it's important to test and ensure that
+ the system is behaving as expected. Using the example above, this
+ demonstrates that the permission system is working properly.
+ </p><pre class="programlisting">
+-- admin can view all rows and fields
+postgres=&gt; set role admin;
+SET
+postgres=&gt; table passwd;
+ user_name | pwhash | uid | gid | real_name | home_phone | extra_info | home_dir | shell
+-----------+--------+-----+-----+-----------+--------------+------------+-------------+-----------
+ admin | xxx | 0 | 0 | Admin | 111-222-3333 | | /root | /bin/dash
+ bob | xxx | 1 | 1 | Bob | 123-456-7890 | | /home/bob | /bin/zsh
+ alice | xxx | 2 | 1 | Alice | 098-765-4321 | | /home/alice | /bin/zsh
+(3 rows)
+
+-- Test what Alice is able to do
+postgres=&gt; set role alice;
+SET
+postgres=&gt; table passwd;
+ERROR: permission denied for table passwd
+postgres=&gt; select user_name,real_name,home_phone,extra_info,home_dir,shell from passwd;
+ user_name | real_name | home_phone | extra_info | home_dir | shell
+-----------+-----------+--------------+------------+-------------+-----------
+ admin | Admin | 111-222-3333 | | /root | /bin/dash
+ bob | Bob | 123-456-7890 | | /home/bob | /bin/zsh
+ alice | Alice | 098-765-4321 | | /home/alice | /bin/zsh
+(3 rows)
+
+postgres=&gt; update passwd set user_name = 'joe';
+ERROR: permission denied for table passwd
+-- Alice is allowed to change her own real_name, but no others
+postgres=&gt; update passwd set real_name = 'Alice Doe';
+UPDATE 1
+postgres=&gt; update passwd set real_name = 'John Doe' where user_name = 'admin';
+UPDATE 0
+postgres=&gt; update passwd set shell = '/bin/xx';
+ERROR: new row violates WITH CHECK OPTION for "passwd"
+postgres=&gt; delete from passwd;
+ERROR: permission denied for table passwd
+postgres=&gt; insert into passwd (user_name) values ('xxx');
+ERROR: permission denied for table passwd
+-- Alice can change her own password; RLS silently prevents updating other rows
+postgres=&gt; update passwd set pwhash = 'abc';
+UPDATE 1
+</pre><p>
+ All of the policies constructed thus far have been permissive policies,
+ meaning that when multiple policies are applied they are combined using
+ the <span class="quote">“<span class="quote">OR</span>”</span> Boolean operator. While permissive policies can be constructed
+ to only allow access to rows in the intended cases, it can be simpler to
+ combine permissive policies with restrictive policies (which the records
+ must pass and which are combined using the <span class="quote">“<span class="quote">AND</span>”</span> Boolean operator).
+ Building on the example above, we add a restrictive policy to require
+ the administrator to be connected over a local Unix socket to access the
+ records of the <code class="literal">passwd</code> table:
+ </p><pre class="programlisting">
+CREATE POLICY admin_local_only ON passwd AS RESTRICTIVE TO admin
+ USING (pg_catalog.inet_client_addr() IS NULL);
+</pre><p>
+ We can then see that an administrator connecting over a network will not
+ see any records, due to the restrictive policy:
+ </p><pre class="programlisting">
+=&gt; SELECT current_user;
+ current_user
+--------------
+ admin
+(1 row)
+
+=&gt; select inet_client_addr();
+ inet_client_addr
+------------------
+ 127.0.0.1
+(1 row)
+
+=&gt; TABLE passwd;
+ user_name | pwhash | uid | gid | real_name | home_phone | extra_info | home_dir | shell
+-----------+--------+-----+-----+-----------+------------+------------+----------+-------
+(0 rows)
+
+=&gt; UPDATE passwd set pwhash = NULL;
+UPDATE 0
+</pre><p>
+ Referential integrity checks, such as unique or primary key constraints
+ and foreign key references, always bypass row security to ensure that
+ data integrity is maintained. Care must be taken when developing
+ schemas and row level policies to avoid <span class="quote">“<span class="quote">covert channel</span>”</span> leaks of
+ information through such referential integrity checks.
+ </p><p>
+ In some contexts it is important to be sure that row security is
+ not being applied. For example, when taking a backup, it could be
+ disastrous if row security silently caused some rows to be omitted
+ from the backup. In such a situation, you can set the
+ <a class="xref" href="runtime-config-client.html#GUC-ROW-SECURITY">row_security</a> configuration parameter
+ to <code class="literal">off</code>. This does not in itself bypass row security;
+ what it does is throw an error if any query's results would get filtered
+ by a policy. The reason for the error can then be investigated and
+ fixed.
+ </p><p>
+ In the examples above, the policy expressions consider only the current
+ values in the row to be accessed or updated. This is the simplest and
+ best-performing case; when possible, it's best to design row security
+ applications to work this way. If it is necessary to consult other rows
+ or other tables to make a policy decision, that can be accomplished using
+ sub-<code class="command">SELECT</code>s, or functions that contain <code class="command">SELECT</code>s,
+ in the policy expressions. Be aware however that such accesses can
+ create race conditions that could allow information leakage if care is
+ not taken. As an example, consider the following table design:
+ </p><pre class="programlisting">
+-- definition of privilege groups
+CREATE TABLE groups (group_id int PRIMARY KEY,
+ group_name text NOT NULL);
+
+INSERT INTO groups VALUES
+ (1, 'low'),
+ (2, 'medium'),
+ (5, 'high');
+
+GRANT ALL ON groups TO alice; -- alice is the administrator
+GRANT SELECT ON groups TO public;
+
+-- definition of users' privilege levels
+CREATE TABLE users (user_name text PRIMARY KEY,
+ group_id int NOT NULL REFERENCES groups);
+
+INSERT INTO users VALUES
+ ('alice', 5),
+ ('bob', 2),
+ ('mallory', 2);
+
+GRANT ALL ON users TO alice;
+GRANT SELECT ON users TO public;
+
+-- table holding the information to be protected
+CREATE TABLE information (info text,
+ group_id int NOT NULL REFERENCES groups);
+
+INSERT INTO information VALUES
+ ('barely secret', 1),
+ ('slightly secret', 2),
+ ('very secret', 5);
+
+ALTER TABLE information ENABLE ROW LEVEL SECURITY;
+
+-- a row should be visible to/updatable by users whose security group_id is
+-- greater than or equal to the row's group_id
+CREATE POLICY fp_s ON information FOR SELECT
+ USING (group_id &lt;= (SELECT group_id FROM users WHERE user_name = current_user));
+CREATE POLICY fp_u ON information FOR UPDATE
+ USING (group_id &lt;= (SELECT group_id FROM users WHERE user_name = current_user));
+
+-- we rely only on RLS to protect the information table
+GRANT ALL ON information TO public;
+</pre><p>
+ Now suppose that <code class="literal">alice</code> wishes to change the <span class="quote">“<span class="quote">slightly
+ secret</span>”</span> information, but decides that <code class="literal">mallory</code> should not
+ be trusted with the new content of that row, so she does:
+ </p><pre class="programlisting">
+BEGIN;
+UPDATE users SET group_id = 1 WHERE user_name = 'mallory';
+UPDATE information SET info = 'secret from mallory' WHERE group_id = 2;
+COMMIT;
+</pre><p>
+ That looks safe; there is no window wherein <code class="literal">mallory</code> should be
+ able to see the <span class="quote">“<span class="quote">secret from mallory</span>”</span> string. However, there is
+ a race condition here. If <code class="literal">mallory</code> is concurrently doing,
+ say,
+</p><pre class="programlisting">
+SELECT * FROM information WHERE group_id = 2 FOR UPDATE;
+</pre><p>
+ and her transaction is in <code class="literal">READ COMMITTED</code> mode, it is possible
+ for her to see <span class="quote">“<span class="quote">secret from mallory</span>”</span>. That happens if her
+ transaction reaches the <code class="structname">information</code> row just
+ after <code class="literal">alice</code>'s does. It blocks waiting
+ for <code class="literal">alice</code>'s transaction to commit, then fetches the updated
+ row contents thanks to the <code class="literal">FOR UPDATE</code> clause. However, it
+ does <span class="emphasis"><em>not</em></span> fetch an updated row for the
+ implicit <code class="command">SELECT</code> from <code class="structname">users</code>, because that
+ sub-<code class="command">SELECT</code> did not have <code class="literal">FOR UPDATE</code>; instead
+ the <code class="structname">users</code> row is read with the snapshot taken at the start
+ of the query. Therefore, the policy expression tests the old value
+ of <code class="literal">mallory</code>'s privilege level and allows her to see the
+ updated row.
+ </p><p>
+ There are several ways around this problem. One simple answer is to use
+ <code class="literal">SELECT ... FOR SHARE</code> in sub-<code class="command">SELECT</code>s in row
+ security policies. However, that requires granting <code class="literal">UPDATE</code>
+ privilege on the referenced table (here <code class="structname">users</code>) to the
+ affected users, which might be undesirable. (But another row security
+ policy could be applied to prevent them from actually exercising that
+ privilege; or the sub-<code class="command">SELECT</code> could be embedded into a security
+ definer function.) Also, heavy concurrent use of row share locks on the
+ referenced table could pose a performance problem, especially if updates
+ of it are frequent. Another solution, practical if updates of the
+ referenced table are infrequent, is to take an
+ <code class="literal">ACCESS EXCLUSIVE</code> lock on the
+ referenced table when updating it, so that no concurrent transactions
+ could be examining old row values. Or one could just wait for all
+ concurrent transactions to end after committing an update of the
+ referenced table and before making changes that rely on the new security
+ situation.
+ </p><p>
+ For additional details see <a class="xref" href="sql-createpolicy.html" title="CREATE POLICY"><span class="refentrytitle">CREATE POLICY</span></a>
+ and <a class="xref" href="sql-altertable.html" title="ALTER TABLE"><span class="refentrytitle">ALTER TABLE</span></a>.
+ </p></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ddl-priv.html" title="5.7. Privileges">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="ddl.html" title="Chapter 5. Data Definition">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="ddl-schemas.html" title="5.9. Schemas">Next</a></td></tr><tr><td width="40%" align="left" valign="top">5.7. Privileges </td><td width="20%" align="center"><a accesskey="h" href="index.html" title="PostgreSQL 16.2 Documentation">Home</a></td><td width="40%" align="right" valign="top"> 5.9. Schemas</td></tr></table></div></body></html> \ No newline at end of file