mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
RLS fixes, new hooks, and new test module
In prepend_row_security_policies(), defaultDeny was always true, so if there were any hook policies, the RLS policies on the table would just get discarded. Fixed to start off with defaultDeny as false and then properly set later if we detect that only the default deny policy exists for the internal policies. The infinite recursion detection in fireRIRrules() didn't properly manage the activeRIRs list in the case of WCOs, so it would incorrectly report infinite recusion if the same relation with RLS appeared more than once in the rtable, for example "UPDATE t ... FROM t ...". Further, the RLS expansion code in fireRIRrules() was handling RLS in the main loop through the rtable, which lead to RTEs being visited twice if they contained sublink subqueries, which prepend_row_security_policies() attempted to handle by exiting early if the RTE already had securityQuals. That doesn't work, however, since if the query involved a security barrier view on top of a table with RLS, the RTE would already have securityQuals (from the view) by the time fireRIRrules() was invoked, and so the table's RLS policies would be ignored. This is fixed in fireRIRrules() by handling RLS in a separate loop at the end, after dealing with any other sublink subqueries, thus ensuring that each RTE is only visited once for RLS expansion. The inheritance planner code didn't correctly handle non-target relations with RLS, which would get turned into subqueries during planning. Thus an update of the form "UPDATE t1 ... FROM t2 ..." where t1 has inheritance and t2 has RLS quals would fail. Fix by making sure to copy in and update the securityQuals when they exist for non-target relations. process_policies() was adding WCOs to non-target relations, which is unnecessary, and could lead to a lot of wasted time in the rewriter and the planner. Fix by only adding WCO policies when working on the result relation. Also in process_policies, we should be copying the USING policies to the WITH CHECK policies on a per-policy basis, fix by moving the copying up into the per-policy loop. Lastly, as noted by Dean, we were simply adding policies returned by the hook provided to the list of quals being AND'd, meaning that they would actually restrict records returned and there was no option to have internal policies and hook-based policies work together permissively (as all internal policies currently work). Instead, explicitly add support for both permissive and restrictive policies by having a hook for each and combining the results appropriately. To ensure this is all done correctly, add a new test module (test_rls_hooks) to test the various combinations of internal, permissive, and restrictive hook policies. Largely from Dean Rasheed (thanks!): CAEZATCVmFUfUOwwhnBTcgi6AquyjQ0-1fyKd0T3xBWJvn+xsFA@mail.gmail.com Author: Dean Rasheed, though I added the new hooks and test module.
This commit is contained in:
170
src/test/modules/test_rls_hooks/test_rls_hooks.c
Normal file
170
src/test/modules/test_rls_hooks/test_rls_hooks.c
Normal file
@ -0,0 +1,170 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
*
|
||||
* test.c
|
||||
* Test harness code for shared memory message queues.
|
||||
*
|
||||
* Copyright (C) 2015, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* src/test/modules/test_rls_hooks/test_rls_hooks.c
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "fmgr.h"
|
||||
#include "miscadmin.h"
|
||||
|
||||
#include "test_rls_hooks.h"
|
||||
|
||||
#include <catalog/pg_type.h>
|
||||
#include <nodes/makefuncs.h>
|
||||
#include <nodes/makefuncs.h>
|
||||
#include <parser/parse_clause.h>
|
||||
#include <parser/parse_node.h>
|
||||
#include <parser/parse_relation.h>
|
||||
#include <rewrite/rowsecurity.h>
|
||||
#include <utils/acl.h>
|
||||
#include <utils/rel.h>
|
||||
#include <utils/relcache.h>
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
/* Saved hook values in case of unload */
|
||||
static row_security_policy_hook_type prev_row_security_policy_hook_permissive = NULL;
|
||||
static row_security_policy_hook_type prev_row_security_policy_hook_restrictive = NULL;
|
||||
|
||||
void _PG_init(void);
|
||||
void _PG_fini(void);
|
||||
|
||||
/* Install hooks */
|
||||
void _PG_init(void)
|
||||
{
|
||||
/* Save values for unload */
|
||||
prev_row_security_policy_hook_permissive = row_security_policy_hook_permissive;
|
||||
prev_row_security_policy_hook_restrictive = row_security_policy_hook_restrictive;
|
||||
|
||||
/* Set our hooks */
|
||||
row_security_policy_hook_permissive = test_rls_hooks_permissive;
|
||||
row_security_policy_hook_restrictive = test_rls_hooks_restrictive;
|
||||
}
|
||||
|
||||
/* Uninstall hooks */
|
||||
void _PG_fini(void)
|
||||
{
|
||||
row_security_policy_hook_permissive = prev_row_security_policy_hook_permissive;
|
||||
row_security_policy_hook_restrictive = prev_row_security_policy_hook_restrictive;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return permissive policies to be added
|
||||
*/
|
||||
List*
|
||||
test_rls_hooks_permissive(CmdType cmdtype, Relation relation)
|
||||
{
|
||||
List *policies = NIL;
|
||||
RowSecurityPolicy *policy = palloc0(sizeof(RowSecurityPolicy));
|
||||
Datum role;
|
||||
FuncCall *n;
|
||||
Node *e;
|
||||
ColumnRef *c;
|
||||
ParseState *qual_pstate;
|
||||
RangeTblEntry *rte;
|
||||
|
||||
if (strcmp(RelationGetRelationName(relation),"rls_test_permissive")
|
||||
&& strcmp(RelationGetRelationName(relation),"rls_test_both"))
|
||||
return NIL;
|
||||
|
||||
qual_pstate = make_parsestate(NULL);
|
||||
|
||||
rte = addRangeTableEntryForRelation(qual_pstate, relation, NULL, false,
|
||||
false);
|
||||
addRTEtoQuery(qual_pstate, rte, false, true, true);
|
||||
|
||||
role = ObjectIdGetDatum(ACL_ID_PUBLIC);
|
||||
|
||||
policy->policy_name = pstrdup("extension policy");
|
||||
policy->policy_id = InvalidOid;
|
||||
policy->polcmd = '*';
|
||||
policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true, 'i');
|
||||
/*
|
||||
policy->qual = (Expr *) makeConst(BOOLOID, -1, InvalidOid,
|
||||
sizeof(bool), BoolGetDatum(true),
|
||||
false, true);
|
||||
*/
|
||||
|
||||
n = makeFuncCall(list_make2(makeString("pg_catalog"),
|
||||
makeString("current_user")), NIL, 0);
|
||||
|
||||
c = makeNode(ColumnRef);
|
||||
c->fields = list_make1(makeString("username"));
|
||||
c->location = 0;
|
||||
|
||||
e = (Node*) makeSimpleA_Expr(AEXPR_OP, "=", (Node*) n, (Node*) c, 0);
|
||||
|
||||
policy->qual = (Expr*) transformWhereClause(qual_pstate, copyObject(e),
|
||||
EXPR_KIND_WHERE,
|
||||
"POLICY");
|
||||
|
||||
policy->with_check_qual = copyObject(policy->qual);
|
||||
policy->hassublinks = false;
|
||||
|
||||
policies = list_make1(policy);
|
||||
|
||||
return policies;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return restrictive policies to be added
|
||||
*/
|
||||
List*
|
||||
test_rls_hooks_restrictive(CmdType cmdtype, Relation relation)
|
||||
{
|
||||
List *policies = NIL;
|
||||
RowSecurityPolicy *policy = palloc0(sizeof(RowSecurityPolicy));
|
||||
Datum role;
|
||||
FuncCall *n;
|
||||
Node *e;
|
||||
ColumnRef *c;
|
||||
ParseState *qual_pstate;
|
||||
RangeTblEntry *rte;
|
||||
|
||||
|
||||
if (strcmp(RelationGetRelationName(relation),"rls_test_restrictive")
|
||||
&& strcmp(RelationGetRelationName(relation),"rls_test_both"))
|
||||
return NIL;
|
||||
|
||||
qual_pstate = make_parsestate(NULL);
|
||||
|
||||
rte = addRangeTableEntryForRelation(qual_pstate, relation, NULL, false,
|
||||
false);
|
||||
addRTEtoQuery(qual_pstate, rte, false, true, true);
|
||||
|
||||
role = ObjectIdGetDatum(ACL_ID_PUBLIC);
|
||||
|
||||
policy->policy_name = pstrdup("extension policy");
|
||||
policy->policy_id = InvalidOid;
|
||||
policy->polcmd = '*';
|
||||
policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true, 'i');
|
||||
|
||||
n = makeFuncCall(list_make2(makeString("pg_catalog"),
|
||||
makeString("current_user")), NIL, 0);
|
||||
|
||||
c = makeNode(ColumnRef);
|
||||
c->fields = list_make1(makeString("supervisor"));
|
||||
c->location = 0;
|
||||
|
||||
e = (Node*) makeSimpleA_Expr(AEXPR_OP, "=", (Node*) n, (Node*) c, 0);
|
||||
|
||||
policy->qual = (Expr*) transformWhereClause(qual_pstate, copyObject(e),
|
||||
EXPR_KIND_WHERE,
|
||||
"POLICY");
|
||||
|
||||
policy->with_check_qual = copyObject(policy->qual);
|
||||
policy->hassublinks = false;
|
||||
|
||||
policies = list_make1(policy);
|
||||
|
||||
return policies;
|
||||
}
|
Reference in New Issue
Block a user