postgresql/src/backend/commands/policy.c

988 lines
27 KiB
C
Raw Normal View History

Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
/*-------------------------------------------------------------------------
*
* policy.c
* Commands for manipulating policies.
*
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/backend/commands/policy.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_rowsecurity.h"
#include "catalog/pg_type.h"
#include "commands/policy.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/pg_list.h"
#include "optimizer/clauses.h"
#include "parser/parse_clause.h"
#include "parser/parse_node.h"
#include "parser/parse_relation.h"
#include "storage/lock.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
static void RangeVarCallbackForPolicy(const RangeVar *rv,
Oid relid, Oid oldrelid, void *arg);
static char parse_row_security_command(const char *cmd_name);
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
static ArrayType* rls_role_list_to_array(List *roles);
/*
* Callback to RangeVarGetRelidExtended().
*
* Checks the following:
* - the relation specified is a table.
* - current user owns the table.
* - the table is not a system table.
*
* If any of these checks fails then an error is raised.
*/
static void
RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
void *arg)
{
HeapTuple tuple;
Form_pg_class classform;
char relkind;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
return;
classform = (Form_pg_class) GETSTRUCT(tuple);
relkind = classform->relkind;
/* Must own relation. */
if (!pg_class_ownercheck(relid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
/* No system table modifications unless explicitly allowed. */
if (!allowSystemTableMods && IsSystemClass(relid, classform))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
rv->relname)));
/* Relation type MUST be a table. */
if (relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
ReleaseSysCache(tuple);
}
/*
* parse_row_security_command -
* helper function to convert full command strings to their char
* representation.
*
* cmd_name - full string command name. Valid values are 'all', 'select',
* 'insert', 'update' and 'delete'.
*
*/
static char
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
parse_row_security_command(const char *cmd_name)
{
char cmd;
if (!cmd_name)
elog(ERROR, "unrecognized command");
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
if (strcmp(cmd_name, "all") == 0)
cmd = 0;
else if (strcmp(cmd_name, "select") == 0)
cmd = ACL_SELECT_CHR;
else if (strcmp(cmd_name, "insert") == 0)
cmd = ACL_INSERT_CHR;
else if (strcmp(cmd_name, "update") == 0)
cmd = ACL_UPDATE_CHR;
else if (strcmp(cmd_name, "delete") == 0)
cmd = ACL_DELETE_CHR;
else
2014-10-07 06:08:59 +02:00
elog(ERROR, "unrecognized command");
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
return cmd;
}
/*
* rls_role_list_to_array
* helper function to convert a list of role names in to an array of
* role ids.
*
* Note: If PUBLIC is provided as a role name, then ACL_ID_PUBLIC is
* used as the role id.
*
* roles - the list of role names to convert.
*/
static ArrayType *
rls_role_list_to_array(List *roles)
{
ArrayType *role_ids;
Datum *temp_array;
ListCell *cell;
int num_roles;
int i = 0;
/* Handle no roles being passed in as being for public */
if (roles == NIL)
{
temp_array = (Datum *) palloc(sizeof(Datum));
temp_array[0] = ObjectIdGetDatum(ACL_ID_PUBLIC);
role_ids = construct_array(temp_array, 1, OIDOID, sizeof(Oid), true,
'i');
return role_ids;
}
num_roles = list_length(roles);
temp_array = (Datum *) palloc(num_roles * sizeof(Datum));
foreach(cell, roles)
{
Oid roleid = get_role_oid_or_public(strVal(lfirst(cell)));
/*
* PUBLIC covers all roles, so it only makes sense alone.
*/
if (roleid == ACL_ID_PUBLIC)
{
if (num_roles != 1)
ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ignoring roles specified other than public"),
errhint("All roles are members of the public role.")));
temp_array[0] = ObjectIdGetDatum(roleid);
num_roles = 1;
break;
}
else
temp_array[i++] = ObjectIdGetDatum(roleid);
}
role_ids = construct_array(temp_array, num_roles, OIDOID, sizeof(Oid), true,
'i');
return role_ids;
}
/*
* Load row-security policy from the catalog, and keep it in
* the relation cache.
*/
void
RelationBuildRowSecurity(Relation relation)
{
Relation catalog;
ScanKeyData skey;
SysScanDesc sscan;
HeapTuple tuple;
MemoryContext oldcxt;
MemoryContext rscxt = NULL;
RowSecurityDesc *rsdesc = NULL;
catalog = heap_open(RowSecurityRelationId, AccessShareLock);
ScanKeyInit(&skey,
Anum_pg_rowsecurity_rsecrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
sscan = systable_beginscan(catalog, RowSecurityRelidPolnameIndexId, true,
NULL, 1, &skey);
PG_TRY();
{
/*
* Set up our memory context- we will always set up some kind of
* policy here. If no explicit policies are found then an implicit
* default-deny policy is created.
*/
rscxt = AllocSetContextCreate(CacheMemoryContext,
"Row-security descriptor",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
rsdesc->rscxt = rscxt;
/*
* Loop through the row-level security entries for this relation, if
* any.
*/
while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
{
Datum value_datum;
char cmd_value;
ArrayType *roles;
char *qual_value;
Expr *qual_expr;
char *with_check_value;
Expr *with_check_qual;
char *policy_name_value;
Oid policy_id;
bool isnull;
RowSecurityPolicy *policy = NULL;
oldcxt = MemoryContextSwitchTo(rscxt);
/* Get policy command */
value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
RelationGetDescr(catalog), &isnull);
if (isnull)
cmd_value = 0;
else
cmd_value = DatumGetChar(value_datum);
/* Get policy name */
value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecpolname,
RelationGetDescr(catalog), &isnull);
Assert(!isnull);
policy_name_value = DatumGetCString(value_datum);
/* Get policy roles */
value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecroles,
RelationGetDescr(catalog), &isnull);
Assert(!isnull);
roles = DatumGetArrayTypeP(value_datum);
/* Get policy qual */
value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
RelationGetDescr(catalog), &isnull);
if (!isnull)
{
qual_value = TextDatumGetCString(value_datum);
qual_expr = (Expr *) stringToNode(qual_value);
}
else
qual_expr = NULL;
/* Get WITH CHECK qual */
value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecwithcheck,
RelationGetDescr(catalog), &isnull);
if (!isnull)
{
with_check_value = TextDatumGetCString(value_datum);
with_check_qual = (Expr *) stringToNode(with_check_value);
}
else
with_check_qual = NULL;
policy_id = HeapTupleGetOid(tuple);
policy = palloc0(sizeof(RowSecurityPolicy));
policy->policy_name = policy_name_value;
policy->rsecid = policy_id;
policy->cmd = cmd_value;
policy->roles = roles;
policy->qual = copyObject(qual_expr);
policy->with_check_qual = copyObject(with_check_qual);
policy->hassublinks = contain_subplans((Node *) qual_expr) ||
contain_subplans((Node *) with_check_qual);
rsdesc->policies = lcons(policy, rsdesc->policies);
MemoryContextSwitchTo(oldcxt);
if (qual_expr != NULL)
pfree(qual_expr);
if (with_check_qual != NULL)
pfree(with_check_qual);
}
/*
* Check if no policies were added
*
* If no policies exist in pg_rowsecurity for this relation, then we
* need to create a single default-deny policy. We use InvalidOid for
* the Oid to indicate that this is the default-deny policy (we may
* decide to ignore the default policy if an extension adds policies).
*/
if (rsdesc->policies == NIL)
{
RowSecurityPolicy *policy = NULL;
Datum role;
oldcxt = MemoryContextSwitchTo(rscxt);
role = ObjectIdGetDatum(ACL_ID_PUBLIC);
policy = palloc0(sizeof(RowSecurityPolicy));
policy->policy_name = pstrdup("default-deny policy");
policy->rsecid = InvalidOid;
policy->cmd = '\0';
policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true,
'i');
policy->qual = (Expr *) makeConst(BOOLOID, -1, InvalidOid,
sizeof(bool), BoolGetDatum(false),
false, true);
policy->with_check_qual = copyObject(policy->qual);
policy->hassublinks = false;
rsdesc->policies = lcons(policy, rsdesc->policies);
MemoryContextSwitchTo(oldcxt);
}
}
PG_CATCH();
{
if (rscxt != NULL)
MemoryContextDelete(rscxt);
PG_RE_THROW();
}
PG_END_TRY();
systable_endscan(sscan);
heap_close(catalog, AccessShareLock);
relation->rsdesc = rsdesc;
}
/*
* RemovePolicyById -
* remove a row-security policy by its OID. If a policy does not exist with
* the provided oid, then an error is raised.
*
* policy_id - the oid of the row-security policy.
*/
void
RemovePolicyById(Oid policy_id)
{
Relation pg_rowsecurity_rel;
SysScanDesc sscan;
ScanKeyData skey[1];
HeapTuple tuple;
Oid relid;
Relation rel;
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
/*
* Find the policy to delete.
*/
ScanKeyInit(&skey[0],
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(policy_id));
sscan = systable_beginscan(pg_rowsecurity_rel, RowSecurityOidIndexId, true,
NULL, 1, skey);
tuple = systable_getnext(sscan);
/* If the policy exists, then remove it, otherwise raise an error. */
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for row-security %u", policy_id);
/*
* Open and exclusive-lock the relation the policy belong to.
*/
relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
RelationGetRelationName(rel))));
if (!allowSystemTableMods && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
simple_heap_delete(pg_rowsecurity_rel, &tuple->t_self);
systable_endscan(sscan);
heap_close(rel, AccessExclusiveLock);
/*
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.
2014-09-24 22:32:22 +02:00
* Note that, unlike some of the other flags in pg_class, relrowsecurity
* is not just an indication of if policies exist. When relrowsecurity
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
* 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
* policy is created and all records are filtered (except for queries from
* the owner).
*/
CacheInvalidateRelcache(rel);
/* Clean up */
heap_close(pg_rowsecurity_rel, RowExclusiveLock);
}
/*
* CreatePolicy -
* handles the execution of the CREATE POLICY command.
*
* stmt - the CreatePolicyStmt that describes the policy to create.
*/
Oid
CreatePolicy(CreatePolicyStmt *stmt)
{
Relation pg_rowsecurity_rel;
Oid rowsec_id;
Relation target_table;
Oid table_id;
char rseccmd;
ArrayType *role_ids;
ParseState *qual_pstate;
ParseState *with_check_pstate;
RangeTblEntry *rte;
Node *qual;
Node *with_check_qual;
ScanKeyData skey[2];
SysScanDesc sscan;
HeapTuple rsec_tuple;
Datum values[Natts_pg_rowsecurity];
bool isnull[Natts_pg_rowsecurity];
ObjectAddress target;
ObjectAddress myself;
/* Parse command */
rseccmd = parse_row_security_command(stmt->cmd);
/*
* If the command is SELECT or DELETE then WITH CHECK should be NULL.
*/
if ((rseccmd == ACL_SELECT_CHR || rseccmd == ACL_DELETE_CHR)
&& stmt->with_check != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WITH CHECK cannot be applied to SELECT or DELETE")));
/*
* If the command is INSERT then WITH CHECK should be the only expression
* provided.
*/
if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
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.
2014-09-24 22:32:22 +02:00
errmsg("only WITH CHECK expression allowed for INSERT")));
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
/* Collect role ids */
role_ids = rls_role_list_to_array(stmt->roles);
/* Parse the supplied clause */
qual_pstate = make_parsestate(NULL);
with_check_pstate = make_parsestate(NULL);
/* zero-clear */
memset(values, 0, sizeof(values));
memset(isnull, 0, sizeof(isnull));
/* Get id of table. Also handles permissions checks. */
table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
false, false,
RangeVarCallbackForPolicy,
(void *) stmt);
/* Open target_table to build quals. No lock is necessary.*/
target_table = relation_open(table_id, NoLock);
/* Add for the regular security quals */
rte = addRangeTableEntryForRelation(qual_pstate, target_table,
NULL, false, false);
addRTEtoQuery(qual_pstate, rte, false, true, true);
/* Add for the with-check quals */
rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
NULL, false, false);
addRTEtoQuery(with_check_pstate, rte, false, true, true);
qual = transformWhereClause(qual_pstate,
copyObject(stmt->qual),
EXPR_KIND_WHERE,
"POLICY");
with_check_qual = transformWhereClause(with_check_pstate,
copyObject(stmt->with_check),
EXPR_KIND_WHERE,
"POLICY");
/* Open pg_rowsecurity catalog */
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
/* Set key - row security relation id. */
ScanKeyInit(&skey[0],
Anum_pg_rowsecurity_rsecrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(table_id));
/* Set key - row security policy name. */
ScanKeyInit(&skey[1],
Anum_pg_rowsecurity_rsecpolname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(stmt->policy_name));
sscan = systable_beginscan(pg_rowsecurity_rel,
RowSecurityRelidPolnameIndexId, true, NULL, 2,
skey);
rsec_tuple = systable_getnext(sscan);
/* Complain if the policy name already exists for the table */
if (HeapTupleIsValid(rsec_tuple))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("policy \"%s\" for relation \"%s\" already exists",
stmt->policy_name, RelationGetRelationName(target_table))));
values[Anum_pg_rowsecurity_rsecrelid - 1] = ObjectIdGetDatum(table_id);
values[Anum_pg_rowsecurity_rsecpolname - 1]
= DirectFunctionCall1(namein, CStringGetDatum(stmt->policy_name));
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
if (rseccmd)
values[Anum_pg_rowsecurity_rseccmd - 1] = CharGetDatum(rseccmd);
else
isnull[Anum_pg_rowsecurity_rseccmd - 1] = true;
values[Anum_pg_rowsecurity_rsecroles - 1] = PointerGetDatum(role_ids);
/* Add qual if present. */
if (qual)
values[Anum_pg_rowsecurity_rsecqual - 1]
= CStringGetTextDatum(nodeToString(qual));
else
isnull[Anum_pg_rowsecurity_rsecqual - 1] = true;
/* Add WITH CHECK qual if present */
if (with_check_qual)
values[Anum_pg_rowsecurity_rsecwithcheck - 1]
= CStringGetTextDatum(nodeToString(with_check_qual));
else
isnull[Anum_pg_rowsecurity_rsecwithcheck - 1] = true;
rsec_tuple = heap_form_tuple(RelationGetDescr(pg_rowsecurity_rel), values,
isnull);
rowsec_id = simple_heap_insert(pg_rowsecurity_rel, rsec_tuple);
/* Update Indexes */
CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple);
/* Record Dependencies */
target.classId = RelationRelationId;
target.objectId = table_id;
target.objectSubId = 0;
myself.classId = RowSecurityRelationId;
myself.objectId = rowsec_id;
myself.objectSubId = 0;
recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable,
DEPENDENCY_NORMAL);
recordDependencyOnExpr(&myself, with_check_qual,
with_check_pstate->p_rtable, DEPENDENCY_NORMAL);
/* Invalidate Relation Cache */
CacheInvalidateRelcache(target_table);
/* Clean up. */
heap_freetuple(rsec_tuple);
free_parsestate(qual_pstate);
free_parsestate(with_check_pstate);
systable_endscan(sscan);
relation_close(target_table, NoLock);
heap_close(pg_rowsecurity_rel, RowExclusiveLock);
return rowsec_id;
}
/*
* AlterPolicy -
* handles the execution of the ALTER POLICY command.
*
* stmt - the AlterPolicyStmt that describes the policy and how to alter it.
*/
Oid
AlterPolicy(AlterPolicyStmt *stmt)
{
Relation pg_rowsecurity_rel;
Oid rowsec_id;
Relation target_table;
Oid table_id;
ArrayType *role_ids = NULL;
List *qual_parse_rtable = NIL;
List *with_check_parse_rtable = NIL;
Node *qual = NULL;
Node *with_check_qual = NULL;
ScanKeyData skey[2];
SysScanDesc sscan;
HeapTuple rsec_tuple;
HeapTuple new_tuple;
Datum values[Natts_pg_rowsecurity];
bool isnull[Natts_pg_rowsecurity];
bool replaces[Natts_pg_rowsecurity];
ObjectAddress target;
ObjectAddress myself;
Datum cmd_datum;
char rseccmd;
bool rseccmd_isnull;
/* Parse role_ids */
if (stmt->roles != NULL)
role_ids = rls_role_list_to_array(stmt->roles);
/* Get id of table. Also handles permissions checks. */
table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
false, false,
RangeVarCallbackForPolicy,
(void *) stmt);
target_table = relation_open(table_id, NoLock);
/* Parse the row-security clause */
if (stmt->qual)
{
RangeTblEntry *rte;
ParseState *qual_pstate = make_parsestate(NULL);
rte = addRangeTableEntryForRelation(qual_pstate, target_table,
NULL, false, false);
addRTEtoQuery(qual_pstate, rte, false, true, true);
qual = transformWhereClause(qual_pstate, copyObject(stmt->qual),
EXPR_KIND_WHERE,
"ROW SECURITY");
qual_parse_rtable = qual_pstate->p_rtable;
free_parsestate(qual_pstate);
}
/* Parse the with-check row-security clause */
if (stmt->with_check)
{
RangeTblEntry *rte;
ParseState *with_check_pstate = make_parsestate(NULL);
rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
NULL, false, false);
addRTEtoQuery(with_check_pstate, rte, false, true, true);
with_check_qual = transformWhereClause(with_check_pstate,
copyObject(stmt->with_check),
EXPR_KIND_WHERE,
"ROW SECURITY");
with_check_parse_rtable = with_check_pstate->p_rtable;
free_parsestate(with_check_pstate);
}
/* zero-clear */
memset(values, 0, sizeof(values));
memset(replaces, 0, sizeof(replaces));
memset(isnull, 0, sizeof(isnull));
/* Find policy to update. */
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
/* Set key - row security relation id. */
ScanKeyInit(&skey[0],
Anum_pg_rowsecurity_rsecrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(table_id));
/* Set key - row security policy name. */
ScanKeyInit(&skey[1],
Anum_pg_rowsecurity_rsecpolname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(stmt->policy_name));
sscan = systable_beginscan(pg_rowsecurity_rel,
RowSecurityRelidPolnameIndexId, true, NULL, 2,
skey);
rsec_tuple = systable_getnext(sscan);
/* Check that the policy is found, raise an error if not. */
if (!HeapTupleIsValid(rsec_tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
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.
2014-09-24 22:32:22 +02:00
errmsg("policy \"%s\" on table \"%s\" does not exist",
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
stmt->policy_name,
RelationGetRelationName(target_table))));
/* Get policy command */
cmd_datum = heap_getattr(rsec_tuple, Anum_pg_rowsecurity_rseccmd,
RelationGetDescr(pg_rowsecurity_rel),
&rseccmd_isnull);
if (rseccmd_isnull)
rseccmd = 0;
else
rseccmd = DatumGetChar(cmd_datum);
/*
* If the command is SELECT or DELETE then WITH CHECK should be NULL.
*/
if ((rseccmd == ACL_SELECT_CHR || rseccmd == ACL_DELETE_CHR)
&& stmt->with_check != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("only USING expression allowed for SELECT, DELETE")));
/*
* If the command is INSERT then WITH CHECK should be the only
* expression provided.
*/
if ((rseccmd == ACL_INSERT_CHR)
&& stmt->qual != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("only WITH CHECK expression allowed for INSERT")));
rowsec_id = HeapTupleGetOid(rsec_tuple);
if (role_ids != NULL)
{
replaces[Anum_pg_rowsecurity_rsecroles - 1] = true;
values[Anum_pg_rowsecurity_rsecroles - 1] = PointerGetDatum(role_ids);
}
if (qual != NULL)
{
replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
values[Anum_pg_rowsecurity_rsecqual - 1]
= CStringGetTextDatum(nodeToString(qual));
}
if (with_check_qual != NULL)
{
replaces[Anum_pg_rowsecurity_rsecwithcheck - 1] = true;
values[Anum_pg_rowsecurity_rsecwithcheck - 1]
= CStringGetTextDatum(nodeToString(with_check_qual));
}
new_tuple = heap_modify_tuple(rsec_tuple,
RelationGetDescr(pg_rowsecurity_rel),
values, isnull, replaces);
simple_heap_update(pg_rowsecurity_rel, &new_tuple->t_self, new_tuple);
/* Update Catalog Indexes */
CatalogUpdateIndexes(pg_rowsecurity_rel, new_tuple);
/* Update Dependencies. */
deleteDependencyRecordsFor(RowSecurityRelationId, rowsec_id, false);
/* Record Dependencies */
target.classId = RelationRelationId;
target.objectId = table_id;
target.objectSubId = 0;
myself.classId = RowSecurityRelationId;
myself.objectId = rowsec_id;
myself.objectSubId = 0;
recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL);
recordDependencyOnExpr(&myself, with_check_qual, with_check_parse_rtable,
DEPENDENCY_NORMAL);
heap_freetuple(new_tuple);
/* Invalidate Relation Cache */
CacheInvalidateRelcache(target_table);
/* Clean up. */
systable_endscan(sscan);
relation_close(target_table, NoLock);
heap_close(pg_rowsecurity_rel, RowExclusiveLock);
return rowsec_id;
}
/*
* rename_policy -
* change the name of a policy on a relation
*/
Oid
rename_policy(RenameStmt *stmt)
{
Relation pg_rowsecurity_rel;
Relation target_table;
Oid table_id;
Oid opoloid;
ScanKeyData skey[2];
SysScanDesc sscan;
HeapTuple rsec_tuple;
/* Get id of table. Also handles permissions checks. */
table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
false, false,
RangeVarCallbackForPolicy,
(void *) stmt);
target_table = relation_open(table_id, NoLock);
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
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.
2014-09-24 22:32:22 +02:00
/* First pass -- check for conflict */
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
/* Add key - row security relation id. */
ScanKeyInit(&skey[0],
Anum_pg_rowsecurity_rsecrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(table_id));
/* Add key - row security policy name. */
ScanKeyInit(&skey[1],
Anum_pg_rowsecurity_rsecpolname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(stmt->newname));
sscan = systable_beginscan(pg_rowsecurity_rel,
RowSecurityRelidPolnameIndexId, true, NULL, 2,
skey);
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.
2014-09-24 22:32:22 +02:00
if (HeapTupleIsValid(systable_getnext(sscan)))
Row-Level Security Policies (RLS) Building on the updatable security-barrier views work, add the ability to define policies on tables to limit the set of rows which are returned from a query and which are allowed to be added to a table. Expressions defined by the policy for filtering are added to the security barrier quals of the query, while expressions defined to check records being added to a table are added to the with-check options of the query. New top-level commands are CREATE/ALTER/DROP POLICY and are controlled by the table owner. Row Security is able to be enabled and disabled by the owner on a per-table basis using ALTER TABLE .. ENABLE/DISABLE ROW SECURITY. Per discussion, ROW SECURITY is disabled on tables by default and must be enabled for policies on the table to be used. If no policies exist on a table with ROW SECURITY enabled, a default-deny policy is used and no records will be visible. By default, row security is applied at all times except for the table owner and the superuser. A new GUC, row_security, is added which can be set to ON, OFF, or FORCE. When set to FORCE, row security will be applied even for the table owner and superusers. When set to OFF, row security will be disabled when allowed and an error will be thrown if the user does not have rights to bypass row security. Per discussion, pg_dump sets row_security = OFF by default to ensure that exports and backups will have all data in the table or will error if there are insufficient privileges to bypass row security. A new option has been added to pg_dump, --enable-row-security, to ask pg_dump to export with row security enabled. A new role capability, BYPASSRLS, which can only be set by the superuser, is added to allow other users to be able to bypass row security using row_security = OFF. Many thanks to the various individuals who have helped with the design, particularly Robert Haas for his feedback. Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean Rasheed, with additional changes and rework by me. Reviewers have included all of the above, Greg Smith, Jeff McCormick, and Robert Haas.
2014-09-19 17:18:35 +02:00
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("row-policy \"%s\" for table \"%s\" already exists",
stmt->newname, RelationGetRelationName(target_table))));
systable_endscan(sscan);
/* Second pass -- find existing policy and update */
/* Add key - row security relation id. */
ScanKeyInit(&skey[0],
Anum_pg_rowsecurity_rsecrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(table_id));
/* Add key - row security policy name. */
ScanKeyInit(&skey[1],
Anum_pg_rowsecurity_rsecpolname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(stmt->subname));
sscan = systable_beginscan(pg_rowsecurity_rel,
RowSecurityRelidPolnameIndexId, true, NULL, 2,
skey);
rsec_tuple = systable_getnext(sscan);
/* Complain if we did not find the policy */
if (!HeapTupleIsValid(rsec_tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("row-policy \"%s\" for table \"%s\" does not exist",
stmt->subname, RelationGetRelationName(target_table))));
opoloid = HeapTupleGetOid(rsec_tuple);
rsec_tuple = heap_copytuple(rsec_tuple);
namestrcpy(&((Form_pg_rowsecurity) GETSTRUCT(rsec_tuple))->rsecpolname,
stmt->newname);
simple_heap_update(pg_rowsecurity_rel, &rsec_tuple->t_self, rsec_tuple);
/* keep system catalog indexes current */
CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple);
InvokeObjectPostAlterHook(RowSecurityRelationId,
HeapTupleGetOid(rsec_tuple), 0);
/*
* Invalidate relation's relcache entry so that other backends (and
* this one too!) are sent SI message to make them rebuild relcache
* entries. (Ideally this should happen automatically...)
*/
CacheInvalidateRelcache(target_table);
/* Clean up. */
systable_endscan(sscan);
heap_close(pg_rowsecurity_rel, RowExclusiveLock);
relation_close(target_table, NoLock);
return opoloid;
}
/*
* get_relation_policy_oid - Look up a policy by name to find its OID
*
* If missing_ok is false, throw an error if policy not found. If
* true, just return InvalidOid.
*/
Oid
get_relation_policy_oid(Oid relid, const char *policy_name, bool missing_ok)
{
Relation pg_rowsecurity_rel;
ScanKeyData skey[2];
SysScanDesc sscan;
HeapTuple rsec_tuple;
Oid policy_oid;
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, AccessShareLock);
/* Add key - row security relation id. */
ScanKeyInit(&skey[0],
Anum_pg_rowsecurity_rsecrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
/* Add key - row security policy name. */
ScanKeyInit(&skey[1],
Anum_pg_rowsecurity_rsecpolname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(policy_name));
sscan = systable_beginscan(pg_rowsecurity_rel,
RowSecurityRelidPolnameIndexId, true, NULL, 2,
skey);
rsec_tuple = systable_getnext(sscan);
if (!HeapTupleIsValid(rsec_tuple))
{
if (!missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("policy \"%s\" for table \"%s\" does not exist",
policy_name, get_rel_name(relid))));
policy_oid = InvalidOid;
}
else
policy_oid = HeapTupleGetOid(rsec_tuple);
/* Clean up. */
systable_endscan(sscan);
heap_close(pg_rowsecurity_rel, AccessShareLock);
return policy_oid;
}