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.
This commit is contained in:
Stephen Frost 2014-09-24 16:32:22 -04:00
parent 3f6f9260e3
commit 6550b901fe
24 changed files with 442 additions and 125 deletions

View File

@ -1941,8 +1941,9 @@
</row>
<row>
<entry><structfield>relhasrowsecurity</structfield></entry>
<entry><structfield>relrowsecurity</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
<entry>
True if table has row-security enabled; see
<link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
@ -5415,7 +5416,7 @@
<note>
<para>
<literal>pg_class.relhasrowsecurity</literal>
<literal>pg_class.relrowsecurity</literal>
True if the table has row-security enabled.
Must be true if the table has a row-security policy in this catalog.
</para>
@ -9228,10 +9229,10 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<entry>True if table has (or once had) triggers</entry>
</row>
<row>
<entry><structfield>hasrowsecurity</structfield></entry>
<entry><structfield>rowsecurity</structfield></entry>
<entry><type>boolean</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhasrowsecurity</literal></entry>
<entry>True if table has row security enabled</entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relrowsecurity</literal></entry>
<entry>True if row security is enabled on the table</entry>
</row>
</tbody>
</tgroup>

View File

@ -5457,9 +5457,9 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
<para>
The allowed values of <varname>row_security</> are
<literal>on</> (apply normally- not to superuser or table owner),
<literal>on</> (apply normally - not to superuser or table owner),
<literal>off</> (fail if row security would be applied), and
<literal>force</> (apply always- even to superuser and table owner).
<literal>force</> (apply always - even to superuser and table owner).
</para>
<para>

View File

@ -1508,6 +1508,174 @@ REVOKE ALL ON accounts FROM PUBLIC;
</para>
</sect1>
<sect1 id="ddl-rowsecurity">
<title>Row Security Policies</title>
<indexterm zone="ddl-rowsecurity">
<primary>rowsecurity</primary>
</indexterm>
<indexterm zone="ddl-rowsecurity">
<primary>rls</primary>
</indexterm>
<indexterm>
<primary>policies</primary>
<see>policy</see>
</indexterm>
<indexterm zone="ddl-rowsecurity">
<primary>POLICY</primary>
</indexterm>
<para>
In addition to the <xref linkend="ddl-priv"> system available through
<xref linkend="sql-grant">, tables can have row security policies
which limit the rows returned for normal queries and rows which can
be added through data modification commands. By default, tables do
not have any policies and all rows are visible and able to be added,
subject to the regular <xref linkend="ddl-priv"> system. This is
also known to as Row Level Security.
</para>
<para>
When row security is enabled on a table with
<xref linkend="sql-altertable">, all normal access to the table
(excluding the owner) for selecting rows or adding rows must be through
a policy. If no policy exists for the table, a default-deny policy is
used and no rows are visible or can be added. Privileges which operate
at the whole table level such as <literal>TRUNCATE</>, and
<literal>REFERENCES</> are not subject to row security.
</para>
<para>
Row security policies can be specific to commands, or to roles, or to
both. The commands available are <literal>SELECT</>, <literal>INSERT</>,
<literal>UPDATE</>, and <literal>DELETE</>. Multiple roles can be
assigned to a given policy and normal role membership and inheiritance
rules apply.
</para>
<para>
To specify which rows are visible and what rows can be added to the
table with row security, an expression is required which returns a
boolean result. This expression will be evaluated for each row prior
to other conditionals or functions which are part of the query. The
one exception to this rule are <literal>leakproof</literal> functions,
which are guaranteed to not leak information. Two expressions may be
specified to provide independent control over the rows which are
visible and the rows which are allowed to be added. The expression
is run as part of the query and with the privileges of the user
running the query, however, security definer functions can be used in
the expression.
</para>
<para>
Enabling and disabling row security, as well as adding policies to a
table, is always the privilege of the owner only.
</para>
<para>
Policies are created using the <xref linkend="sql-createpolicy">
command, altered using the <xref linkend="sql-alterpolicy"> command,
and dropped using the <xref linkend="sql-droppolicy"> command. To
enable and disable row security for a given table, use the
<xref linkend="sql-altertable"> command.
</para>
<para>
The table owners and superusers bypass the row security system when
querying a table, by default. Row security can be enabled for
superusers and table owners by setting
<xref linkend="guc-row-security"> to <literal>force</literal>. Any
user can request that row security be bypassed by setting
<xref linkend="guc-row-security"> to <literal>off</literal>. If
the user does not have privileges to bypass row security when
querying a given table then an error will be returned instead. Other
users can be granted the ability to bypass the row security system
with the <literal>BYPASSRLS</literal> role attribute. This
attribute can only be set by a superuser.
</para>
<para>
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.
</para>
<para>
When multiple policies apply to a given query, they are combined using
<literal>OR</literal>, similar to how a given role has the privileges
of all roles which they are a member of.
</para>
<para>
Referential integrity checks, such as unique or primary key constraints
and foreign key references, will bypass row security to ensure that
data integrity is maintained. Care must be taken when developing
schemas and row level policies to avoid a "covert channel" leak of
information through these referntial integrity checks.
</para>
<para>
To enable row security for a table,
the <command>ALTER TABLE</command> is used. For example, to enable
row level security for the table accounts, use:
</para>
<programlisting>
-- Create the table first
CREATE TABLE accounts (manager text, company text, contact_email text);
ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
</programlisting>
<para>
To create a policy on the account relation to allow the managers role
to view the rows of their accounts, the <command>CREATE POLICY</command>
command can be used:
</para>
<programlisting>
CREATE POLICY account_managers ON accounts TO managers
USING (manager = current_user);
</programlisting>
<para>
If no role is specified, or the special <quote>user</quote> name
<literal>PUBLIC</literal> is used, then the policy applies to all
users on the system. To allow all users to view their own row in
a user table, a simple policy can be used:
</para>
<programlisting>
CREATE POLICY user_policy ON users
USING (user = current_user);
</programlisting>
<para>
To use a different policy for rows which are being added to the
table from those rows which are visible, the WITH CHECK clause
can be used. This would allow all users to view all rows in the
users table, but only modify their own:
</para>
<programlisting>
CREATE POLICY user_policy ON users
USING (true)
WITH CHECK (user = current_user);
</programlisting>
<para>
Row security can be disabled with the <command>ALTER TABLE</command>
also. Note that disabling row security does not remove the
policies which are defined on the table, they are simply ignored
and all rows are visible and able to be added, subject to the
normal privileges system.
</para>
</sect1>
<sect1 id="ddl-schemas">
<title>Schemas</title>

View File

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

View File

@ -240,7 +240,7 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
</varlistentry>
<varlistentry id="SQL-CREATEPOLICY-UPDATE">
<term><literal>DELETE</></term>
<term><literal>UPDATE</></term>
<listitem>
<para>
Using <literal>UPDATE</literal> for a policy means that it will apply

View File

@ -687,6 +687,23 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry>
<term><option>--enable-row-security</></term>
<listitem>
<para>
This option is relevant only when dumping the contents of a table
which has row security. By default, pg_dump will set
<literal>ROW_SECURITY</literal> to <literal>OFF</literal>, to ensure
that all data is dumped from the table. If the user does not have
sufficient privileges to bypass row security, then an error is thrown.
This parameter instructs <application>pg_dump</application> to set
row_security to 'ON' instead, allowing the user to dump the contents
of the table which they have access to.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--exclude-table-data=<replaceable class="parameter">table</replaceable></option></term>
<listitem>

View File

@ -490,6 +490,29 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--enable-row-security</></term> <listitem>
<para>
This option is relevant only when restoring the contents of a table
which has row security. By default, pg_restore will set
<literal>ROW_SECURITY</literal> to <literal>OFF</literal>, to ensure
that all data is restored in to the table. If the user does not have
sufficient privileges to bypass row security, then an error is thrown.
This parameter instructs <application>pg_restore</application> to set
row_security to 'ON' instead, allowing the user to attempt to restore
the contents of the table with row security enabled. This may still
fail if the user does not have the right to insert the rows from the
dump into the table.
</para>
<para>
Note that this option currently also requires the dump be in INSERT
format as COPY TO does not support row security.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--if-exists</option></term>
<listitem>

View File

@ -799,7 +799,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
values[Anum_pg_class_relrowsecurity - 1] = BoolGetDatum(rd_rel->relrowsecurity);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);

View File

@ -119,7 +119,7 @@ CREATE VIEW pg_tables AS
C.relhasindex AS hasindexes,
C.relhasrules AS hasrules,
C.relhastriggers AS hastriggers,
C.relhasrowsecurity AS hasrowsecurity
C.relrowsecurity AS rowsecurity
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
WHERE C.relkind = 'r';

View File

@ -108,7 +108,7 @@ parse_row_security_command(const char *cmd_name)
char cmd;
if (!cmd_name)
elog(ERROR, "Unregonized command.");
elog(ERROR, "unregonized command");
if (strcmp(cmd_name, "all") == 0)
cmd = 0;
@ -121,8 +121,7 @@ parse_row_security_command(const char *cmd_name)
else if (strcmp(cmd_name, "delete") == 0)
cmd = ACL_DELETE_CHR;
else
elog(ERROR, "Unregonized command.");
/* error unrecognized command */
elog(ERROR, "unregonized command");
return cmd;
}
@ -422,8 +421,8 @@ RemovePolicyById(Oid policy_id)
heap_close(rel, AccessExclusiveLock);
/*
* Note that, unlike some of the other flags in pg_class, relhasrowsecurity
* is not just an indication of if policies exist. When relhasrowsecurity
* Note that, unlike some of the other flags in pg_class, relrowsecurity
* is not just an indication of if policies exist. When relrowsecurity
* is set (which can be done directly by the user or indirectly by creating
* a policy on the table), then all access to the relation must be through
* a policy. If no policy is defined for the relation then a default-deny
@ -484,7 +483,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Only WITH CHECK expression allowed for INSERT")));
errmsg("only WITH CHECK expression allowed for INSERT")));
/* Collect role ids */
@ -731,7 +730,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
if (!HeapTupleIsValid(rsec_tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("policy '%s' for does not exist on table %s",
errmsg("policy \"%s\" on table \"%s\" does not exist",
stmt->policy_name,
RelationGetRelationName(target_table))));
@ -850,7 +849,7 @@ rename_policy(RenameStmt *stmt)
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
/* First pass- check for conflict */
/* First pass -- check for conflict */
/* Add key - row security relation id. */
ScanKeyInit(&skey[0],
@ -868,7 +867,7 @@ rename_policy(RenameStmt *stmt)
RowSecurityRelidPolnameIndexId, true, NULL, 2,
skey);
if (HeapTupleIsValid(rsec_tuple = systable_getnext(sscan)))
if (HeapTupleIsValid(systable_getnext(sscan)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("row-policy \"%s\" for table \"%s\" already exists",

View File

@ -10647,7 +10647,7 @@ ATExecEnableRowSecurity(Relation rel)
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = true;
((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = true;
simple_heap_update(pg_class, &tuple->t_self, tuple);
/* keep catalog indexes current */
@ -10674,7 +10674,7 @@ ATExecDisableRowSecurity(Relation rel)
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = false;
((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = false;
simple_heap_update(pg_class, &tuple->t_self, tuple);
/* keep catalog indexes current */

View File

@ -61,7 +61,7 @@ static void process_policies(List *policies, int rt_index,
Expr **final_qual,
Expr **final_with_check_qual,
bool *hassublinks);
static bool check_role_for_policy(RowSecurityPolicy *policy, Oid user_id);
static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id);
/*
* hook to allow extensions to apply their own security policy
@ -177,7 +177,7 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
* all of them OR'd together. However, to avoid the situation of an
* extension granting more access to a table than the internal policies
* would allow, the extension's policies are AND'd with the internal
* policies. In other words- extensions can only provide further
* policies. In other words - extensions can only provide further
* filtering of the result set (or further reduce the set of records
* allowed to be added).
*
@ -305,7 +305,8 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
policy = (RowSecurityPolicy *) lfirst(item);
/* Always add ALL policies, if they exist. */
if (policy->cmd == '\0' && check_role_for_policy(policy, user_id))
if (policy->cmd == '\0' &&
check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
/* Build the list of policies to return. */
@ -313,23 +314,23 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
{
case CMD_SELECT:
if (policy->cmd == ACL_SELECT_CHR
&& check_role_for_policy(policy, user_id))
&& check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
break;
case CMD_INSERT:
/* If INSERT then only need to add the WITH CHECK qual */
if (policy->cmd == ACL_INSERT_CHR
&& check_role_for_policy(policy, user_id))
&& check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
break;
case CMD_UPDATE:
if (policy->cmd == ACL_UPDATE_CHR
&& check_role_for_policy(policy, user_id))
&& check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
break;
case CMD_DELETE:
if (policy->cmd == ACL_DELETE_CHR
&& check_role_for_policy(policy, user_id))
&& check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
break;
default:
@ -473,7 +474,7 @@ check_enable_rls(Oid relid, Oid checkAsUser)
{
HeapTuple tuple;
Form_pg_class classform;
bool relhasrowsecurity;
bool relrowsecurity;
Oid user_id = checkAsUser ? checkAsUser : GetUserId();
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
@ -482,12 +483,12 @@ check_enable_rls(Oid relid, Oid checkAsUser)
classform = (Form_pg_class) GETSTRUCT(tuple);
relhasrowsecurity = classform->relhasrowsecurity;
relrowsecurity = classform->relrowsecurity;
ReleaseSysCache(tuple);
/* Nothing to do if the relation does not have RLS */
if (!relhasrowsecurity)
if (!relrowsecurity)
return RLS_NONE;
/*
@ -537,19 +538,19 @@ check_enable_rls(Oid relid, Oid checkAsUser)
* check_role_for_policy -
* determines if the policy should be applied for the current role
*/
bool
check_role_for_policy(RowSecurityPolicy *policy, Oid user_id)
static bool
check_role_for_policy(ArrayType *policy_roles, Oid user_id)
{
int i;
Oid *roles = (Oid *) ARR_DATA_PTR(policy->roles);
Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles);
/* Quick fall-thru for policies applied to all roles */
if (roles[0] == ACL_ID_PUBLIC)
return true;
for (i = 0; i < ARR_DIMS(policy->roles)[0]; i++)
for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++)
{
if (is_member_of_role(user_id, roles[i]))
if (has_privs_of_role(user_id, roles[i]))
return true;
}

View File

@ -2309,9 +2309,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
* have RLS enabled.
*/
if (!has_bypassrls_privilege(GetUserId()) &&
((pk_rel->rd_rel->relhasrowsecurity &&
((pk_rel->rd_rel->relrowsecurity &&
!pg_class_ownercheck(pkrte->relid, GetUserId())) ||
(fk_rel->rd_rel->relhasrowsecurity &&
(fk_rel->rd_rel->relrowsecurity &&
!pg_class_ownercheck(fkrte->relid, GetUserId()))))
return false;

View File

@ -847,6 +847,87 @@ equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
return true;
}
/*
* equalPolicy
*
* Determine whether two policies are equivalent
*/
static bool
equalPolicy(RowSecurityPolicy *policy1, RowSecurityPolicy *policy2)
{
int i;
Oid *r1,
*r2;
if (policy1 != NULL)
{
if (policy2 == NULL)
return false;
if (policy1->rsecid != policy2->rsecid)
return false;
if (policy1->cmd != policy2->cmd)
return false;
if (policy1->hassublinks != policy2->hassublinks);
return false;
if (strcmp(policy1->policy_name,policy2->policy_name) != 0)
return false;
if (ARR_DIMS(policy1->roles)[0] != ARR_DIMS(policy2->roles)[0])
return false;
r1 = (Oid *) ARR_DATA_PTR(policy1->roles);
r2 = (Oid *) ARR_DATA_PTR(policy2->roles);
for (i = 0; i < ARR_DIMS(policy1->roles)[0]; i++)
{
if (r1[i] != r2[i])
return false;
}
if (!equal(policy1->qual, policy1->qual))
return false;
if (!equal(policy1->with_check_qual, policy2->with_check_qual))
return false;
}
else if (policy2 != NULL)
return false;
return true;
}
/*
* equalRSDesc
*
* Determine whether two RowSecurityDesc's are equivalent
*/
static bool
equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
{
ListCell *lc,
*rc;
if (rsdesc1 == NULL && rsdesc2 == NULL)
return true;
if ((rsdesc1 != NULL && rsdesc2 == NULL) ||
(rsdesc1 == NULL && rsdesc2 != NULL))
return false;
if (list_length(rsdesc1->policies) != list_length(rsdesc2->policies))
return false;
/* RelationBuildRowSecurity should build policies in order */
forboth(lc, rsdesc1->policies, rc, rsdesc2->policies)
{
RowSecurityPolicy *l = (RowSecurityPolicy *) lfirst(lc);
RowSecurityPolicy *r = (RowSecurityPolicy *) lfirst(rc);
if (!equalPolicy(l,r))
return false;
}
return false;
}
/*
* RelationBuildDesc
@ -967,7 +1048,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->trigdesc = NULL;
if (relation->rd_rel->relhasrowsecurity)
if (relation->rd_rel->relrowsecurity)
RelationBuildRowSecurity(relation);
else
relation->rsdesc = NULL;
@ -2104,6 +2185,7 @@ RelationClearRelation(Relation relation, bool rebuild)
Oid save_relid = RelationGetRelid(relation);
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@ -2117,6 +2199,7 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rsdesc, newrel->rsdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@ -2165,6 +2248,8 @@ RelationClearRelation(Relation relation, bool rebuild)
SWAPFIELD(RuleLock *, rd_rules);
SWAPFIELD(MemoryContext, rd_rulescxt);
}
if (keep_policies)
SWAPFIELD(RowSecurityDesc *, rsdesc);
/* toast OID override must be preserved */
SWAPFIELD(Oid, rd_toastoid);
/* pgstat_info must be preserved */
@ -3345,11 +3430,11 @@ RelationCacheInitializePhase3(void)
/*
* Re-load the row security policies if the relation has them, since
* they are not preserved in the cache. Note that we can never NOT
* have a policy while relhasrowsecurity is true-
* have a policy while relrowsecurity is true,
* RelationBuildRowSecurity will create a single default-deny policy
* if there is no policy defined in pg_rowsecurity.
*/
if (relation->rd_rel->relhasrowsecurity && relation->rsdesc == NULL)
if (relation->rd_rel->relrowsecurity && relation->rsdesc == NULL)
{
RelationBuildRowSecurity(relation);

View File

@ -376,10 +376,13 @@ RestoreArchive(Archive *AHX)
/*
* Enable row-security if necessary.
*/
if (!ropt->enable_row_security)
ahprintf(AH, "SET row_security = off;\n");
else
ahprintf(AH, "SET row_security = on;\n");
if (PQserverVersion(AH->connection) >= 90500)
{
if (!ropt->enable_row_security)
ahprintf(AH, "SET row_security = off;\n");
else
ahprintf(AH, "SET row_security = on;\n");
}
/*
* Establish important parameter values right away.

View File

@ -2777,7 +2777,7 @@ dumpBlobs(Archive *fout, void *arg)
void
getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
{
PQExpBuffer query = createPQExpBuffer();
PQExpBuffer query;
PGresult *res;
RowSecurityInfo *rsinfo;
int i_oid;
@ -2792,6 +2792,8 @@ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
if (fout->remoteVersion < 90500)
return;
query = createPQExpBuffer();
for (i = 0; i < numTables; i++)
{
TableInfo *tbinfo = &tblinfo[i];
@ -2809,7 +2811,7 @@ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
* We represent RLS enabled on a table by creating RowSecurityInfo
* object with an empty policy.
*/
if (tbinfo->hasrowsec)
if (tbinfo->rowsec)
{
/*
* Note: use tableoid 0 so that this object won't be mistaken for
@ -4534,7 +4536,7 @@ getTables(Archive *fout, int *numTables)
int i_relhastriggers;
int i_relhasindex;
int i_relhasrules;
int i_relhasrowsec;
int i_relrowsec;
int i_relhasoids;
int i_relfrozenxid;
int i_relminmxid;
@ -4588,7 +4590,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relhasrowsecurity, "
"c.relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
@ -4629,7 +4631,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
@ -4670,7 +4672,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
@ -4711,7 +4713,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
@ -4750,7 +4752,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
@ -4788,7 +4790,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
@ -4826,7 +4828,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
@ -4864,7 +4866,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@ -4901,7 +4903,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@ -4934,7 +4936,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@ -4962,7 +4964,7 @@ getTables(Archive *fout, int *numTables)
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, "
"'t'::bool AS relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@ -5000,7 +5002,7 @@ getTables(Archive *fout, int *numTables)
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, "
"'t'::bool AS relhasoids, "
"'f'::bool AS relhasrowsecurity, "
"'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@ -5048,7 +5050,7 @@ getTables(Archive *fout, int *numTables)
i_relhastriggers = PQfnumber(res, "relhastriggers");
i_relhasindex = PQfnumber(res, "relhasindex");
i_relhasrules = PQfnumber(res, "relhasrules");
i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
i_relrowsec = PQfnumber(res, "relrowsecurity");
i_relhasoids = PQfnumber(res, "relhasoids");
i_relfrozenxid = PQfnumber(res, "relfrozenxid");
i_relminmxid = PQfnumber(res, "relminmxid");
@ -5100,7 +5102,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
tblinfo[i].rowsec = (strcmp(PQgetvalue(res, i, i_relrowsec), "t") == 0);
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));

View File

@ -246,7 +246,7 @@ typedef struct _tableInfo
bool hasindex; /* does it have any indexes? */
bool hasrules; /* does it have any rules? */
bool hastriggers; /* does it have any triggers? */
bool hasrowsec; /* does it have any row-security policy? */
bool rowsec; /* does it have any row-security policy? */
bool hasoids; /* does it have OIDs? */
uint32 frozenxid; /* for restore frozen xid */
uint32 minmxid; /* for restore min multi xid */

View File

@ -463,7 +463,7 @@ usage(const char *progname)
printf(_(" -x, --no-privileges skip restoration of access privileges (grant/revoke)\n"));
printf(_(" -1, --single-transaction restore as a single transaction\n"));
printf(_(" --disable-triggers disable triggers during data-only restore\n"));
printf(_(" --enable-row-security enable row level security\n"));
printf(_(" --enable-row-security enable row level security\n"));
printf(_(" --if-exists use IF EXISTS when dropping objects\n"));
printf(_(" --no-data-for-failed-tables do not restore data of tables that could not be\n"
" created\n"));

View File

@ -1204,7 +1204,7 @@ describeOneTableDetails(const char *schemaname,
bool hasindex;
bool hasrules;
bool hastriggers;
bool hasrowsecurity;
bool rowsecurity;
bool hasoids;
Oid tablespace;
char *reloptions;
@ -1230,7 +1230,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, c.relhasrowsecurity, c.relhasoids, "
"c.relhastriggers, c.relrowsecurity, c.relhasoids, "
"%s, c.reltablespace, "
"CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
"c.relpersistence, c.relreplident\n"
@ -1355,7 +1355,7 @@ describeOneTableDetails(const char *schemaname,
tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 2), "t") == 0;
tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0;
tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
tableinfo.hasrowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
tableinfo.rowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 6), "t") == 0;
tableinfo.reloptions = (pset.sversion >= 80200) ?
pg_strdup(PQgetvalue(res, 0, 7)) : NULL;
@ -1998,18 +1998,17 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
/* print any row-level policies */
if (pset.sversion >= 90500)
{
appendPQExpBuffer(&buf,
",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
gettext_noop("Row-security"));
if (verbose && pset.sversion >= 90500)
appendPQExpBuffer(&buf,
"\n LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
/* print any row-level policies */
if (tableinfo.hasrowsecurity)
{
if (verbose)
appendPQExpBuffer(&buf,
"\n LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
printfPQExpBuffer(&buf,
"SELECT rs.rsecpolname,\n"
"CASE WHEN rs.rsecroles = '{0}' THEN NULL ELSE array(select rolname from pg_roles where oid = any (rs.rsecroles) order by 1) END,\n"
@ -2019,41 +2018,53 @@ describeOneTableDetails(const char *schemaname,
"FROM pg_catalog.pg_rowsecurity rs\n"
"WHERE rs.rsecrelid = '%s' ORDER BY 1;",
oid);
result = PSQLexec(buf.data, false);
if (!result)
goto error_return;
else
tuples = PQntuples(result);
if (tuples > 0)
{
/*
* Handle cases where RLS is enabled and there are policies,
* or there aren't policies, or RLS isn't enabled but there
* are policies
*/
if (tableinfo.rowsecurity && tuples > 0)
printTableAddFooter(&cont, _("Policies:"));
for (i = 0; i < tuples; i++)
if (tableinfo.rowsecurity && tuples == 0)
printTableAddFooter(&cont, _("Policies (Row Security Enabled): (None)"));
if (!tableinfo.rowsecurity && tuples > 0)
printTableAddFooter(&cont, _("Policies (Row Security Disabled):"));
/* Might be an empty set - that's ok */
for (i = 0; i < tuples; i++)
{
printfPQExpBuffer(&buf, " POLICY \"%s\"",
PQgetvalue(result, i, 0));
if (!PQgetisnull(result, i, 4))
appendPQExpBuffer(&buf, " (%s)",
PQgetvalue(result, i, 4));
if (!PQgetisnull(result, i, 2))
appendPQExpBuffer(&buf, " EXPRESSION %s",
PQgetvalue(result, i, 2));
if (!PQgetisnull(result, i, 3))
appendPQExpBuffer(&buf, " WITH CHECK %s",
PQgetvalue(result, i, 3));
printTableAddFooter(&cont, buf.data);
if (!PQgetisnull(result, i, 1))
{
printfPQExpBuffer(&buf, " POLICY \"%s\"",
PQgetvalue(result, i, 0));
if (!PQgetisnull(result, i, 4))
appendPQExpBuffer(&buf, " (%s)",
PQgetvalue(result, i, 4));
if (!PQgetisnull(result, i, 2))
appendPQExpBuffer(&buf, " EXPRESSION %s",
PQgetvalue(result, i, 2));
if (!PQgetisnull(result, i, 3))
appendPQExpBuffer(&buf, " WITH CHECK %s",
PQgetvalue(result, i, 3));
printfPQExpBuffer(&buf, " APPLIED TO %s",
PQgetvalue(result, i, 1));
printTableAddFooter(&cont, buf.data);
if (!PQgetisnull(result, i, 1))
{
printfPQExpBuffer(&buf, " APPLIED TO %s",
PQgetvalue(result, i, 1));
printTableAddFooter(&cont, buf.data);
}
}
}
PQclear(result);
@ -2708,6 +2719,10 @@ describeRoles(const char *pattern, bool verbose)
if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
add_role_attribute(&buf, _("Replication"));
if (pset.sversion >= 90500)
if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
add_role_attribute(&buf, _("Bypass RLS"));
conns = atoi(PQgetvalue(res, i, 6));
if (conns >= 0)
{

View File

@ -1214,11 +1214,12 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev2_wd, "ROLE") == 0))
{
static const char *const list_ALTERUSER[] =
{"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
"ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
"NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
"NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
"SUPERUSER", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
"CREATEUSER", "ENCRYPTED", "INHERIT", "LOGIN", "NOBYPASSRLS",
"NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "RENAME TO",
"REPLICATION", "RESET", "SET", "SUPERUSER", "UNENCRYPTED",
"VALID UNTIL", "WITH", NULL};
COMPLETE_WITH_LIST(list_ALTERUSER);
}
@ -1231,11 +1232,12 @@ psql_completion(const char *text, int start, int end)
{
/* Similar to the above, but don't complete "WITH" again. */
static const char *const list_ALTERUSER_WITH[] =
{"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
"ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
"NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
"NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
"SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL};
{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
"CREATEUSER", "ENCRYPTED", "INHERIT", "LOGIN", "NOBYPASSRLS",
"NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "RENAME TO",
"REPLICATION", "RESET", "SET", "SUPERUSER", "UNENCRYPTED",
"VALID UNTIL", NULL};
COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
}
@ -2565,10 +2567,10 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
{
static const char *const list_CREATEROLE[] =
{"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
"ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
"NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
"NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
"CREATEUSER", "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOBYPASSRLS",
"NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
"SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
COMPLETE_WITH_LIST(list_CREATEROLE);
@ -2583,10 +2585,10 @@ psql_completion(const char *text, int start, int end)
{
/* Similar to the above, but don't complete "WITH" again. */
static const char *const list_CREATEROLE_WITH[] =
{"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
"ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
"NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
"NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
"CREATEUSER", "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOBYPASSRLS",
"NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
"SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
COMPLETE_WITH_LIST(list_CREATEROLE_WITH);

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201409191
#define CATALOG_VERSION_NO 201409241
#endif

View File

@ -65,7 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
bool relhasrules; /* has (or has had) any rules */
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
bool relhasrowsecurity; /* has (or has had) row-security policy */
bool relrowsecurity; /* row-security is enabled or not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@ -119,7 +119,7 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23
#define Anum_pg_class_relhasrowsecurity 24
#define Anum_pg_class_relrowsecurity 24
#define Anum_pg_class_relispopulated 25
#define Anum_pg_class_relreplident 26
#define Anum_pg_class_relfrozenxid 27

View File

@ -16,6 +16,7 @@
#define POLICY_H
#include "nodes/parsenodes.h"
#include "utils/relcache.h"
extern void RelationBuildRowSecurity(Relation relation);
@ -24,10 +25,10 @@ extern void RemovePolicyById(Oid policy_id);
extern Oid CreatePolicy(CreatePolicyStmt *stmt);
extern Oid AlterPolicy(AlterPolicyStmt *stmt);
Oid get_relation_policy_oid(Oid relid,
const char *policy_name, bool missing_ok);
extern Oid get_relation_policy_oid(Oid relid, const char *policy_name,
bool missing_ok);
Oid rename_policy(RenameStmt *stmt);
extern Oid rename_policy(RenameStmt *stmt);
#endif /* POLICY_H */

View File

@ -2046,7 +2046,7 @@ pg_tables| SELECT n.nspname AS schemaname,
c.relhasindex AS hasindexes,
c.relhasrules AS hasrules,
c.relhastriggers AS hastriggers,
c.relhasrowsecurity AS hasrowsecurity
c.relrowsecurity AS rowsecurity
FROM ((pg_class c
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))