openGauss-server/contrib/security_plugin/gs_policy_logical_tree.cpp

435 lines
15 KiB
C++

/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* gs_policy_logical_tree.cpp
* dealing polish-notation format string into policy logical node of policy
* plugin for gaussdb kernel
*
* IDENTIFICATION
* contrib/security_plugin/gs_policy_logical_tree.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "gs_policy_logical_tree.h"
#include <list>
#include "utils/lsyscache.h"
#include "utils/builtins.h"
#include "utils/acl.h"
PolicyLogicalNode &PolicyLogicalNode::operator =(const PolicyLogicalNode &arg)
{
if (&arg == this) {
return *this;
}
m_type = arg.m_type;
m_apps = arg.m_apps;
m_roles = arg.m_roles;
m_has_operator_NOT = arg.m_has_operator_NOT;
m_left = arg.m_left;
m_right = arg.m_right;
m_eval_res = arg.m_eval_res;
m_ip_range = arg.m_ip_range;
return *this;
}
bool PolicyLogicalNode::operator <(const PolicyLogicalNode &arg) const
{
return true;
}
void PolicyLogicalNode::make_eval(const FilterData *filter_item)
{
switch (m_type) {
case E_FILTER_ROLE_NODE: /* filter type is role */
m_eval_res = (m_roles.find(GetCurrentUserId()) != m_roles.end());
break;
case E_FILTER_APP_NODE: /* filter type is app */
m_eval_res = (m_apps.find(filter_item->m_app.c_str()) != m_apps.end());
break;
case E_FILTER_IP_NODE: /* filter type is ip */
m_eval_res = m_ip_range.is_in_range(&filter_item->m_ip);
break;
default:
/* should not get here */
m_eval_res = false;
break;
}
if (m_has_operator_NOT) {
m_eval_res = !m_eval_res;
}
}
/* Parses polish-notation format string into policy logical node */
static bool parse_values(const gs_stl::gs_string logical_expr_str, int *offset, PolicyLogicalNode *root)
{
std::size_t found = gs_stl::gs_string::npos;
char buff[512] = {0};
int nRet;
size_t limit_pos = logical_expr_str.find(']', *offset);
if (limit_pos == gs_stl::gs_string::npos) {
return false;
}
while ((found = logical_expr_str.find(',', *offset)) != gs_stl::gs_string::npos && found < limit_pos) {
nRet = snprintf_s(buff, sizeof(buff), sizeof(buff) - 1, "%.*s", (int)(found - *offset),
logical_expr_str.c_str() + *offset);
securec_check_ss(nRet, "\0", "\0");
switch (root->m_type) {
case E_FILTER_IP_NODE:
root->m_ip_range.add_range(buff, strlen(buff));
break;
case E_FILTER_ROLE_NODE:
root->m_roles.push_back(isdigit(buff[0]) ? atol(buff) : get_role_oid(buff, true));
break;
default:
root->m_apps.push_back(buff);
break;
}
*offset = found + 1;
}
if (*offset < (int)limit_pos) {
nRet = snprintf_s(buff, sizeof(buff), sizeof(buff) - 1, "%.*s", (int)(limit_pos - *offset),
logical_expr_str.c_str() + *offset);
securec_check_ss(nRet, "\0", "\0");
switch (root->m_type) {
case E_FILTER_IP_NODE:
root->m_ip_range.add_range(buff, strlen(buff));
break;
case E_FILTER_ROLE_NODE:
{
root->m_roles.push_back(isdigit(buff[0]) ? atol(buff) : get_role_oid(buff, true));
}
break;
default:
root->m_apps.push_back(buff);
break;
}
*offset = limit_pos + 1;
return true;
}
return false;
}
/* C-tor */
PolicyLogicalTree::PolicyLogicalTree() : m_has_ip(false), m_has_role(false), m_has_app(false) { }
/* D-tor */
PolicyLogicalTree::~PolicyLogicalTree()
{
reset();
}
PolicyLogicalTree &PolicyLogicalTree::operator =(const PolicyLogicalTree &arg)
{
if (&arg == this) {
return *this;
}
reset();
for (size_t i = 0; i < arg.m_nodes.size(); ++i) {
m_nodes.push_back(arg.m_nodes[i]);
}
flatten_tree();
m_has_ip = arg.m_has_ip;
m_has_role = arg.m_has_role;
m_has_app = arg.m_has_app;
return *this;
}
/* Resets logical tree vector */
void PolicyLogicalTree::reset()
{
m_nodes.clear();
m_flat_tree.clear();
}
/* Matches logical tree with provided filter item */
bool PolicyLogicalTree::match(const FilterData *filter_item)
{
/* optimizations */
if (m_flat_tree.size() == 0) {
return false;
}
size_t nodes_num = m_flat_tree.size();
while (nodes_num > 0) {
int idx = m_flat_tree[nodes_num - 1];
PolicyLogicalNode *item = &m_nodes[idx];
switch (item->m_type) {
case E_AND_NODE:
item->m_eval_res = m_nodes[item->m_left].m_eval_res && m_nodes[item->m_right].m_eval_res;
break;
case E_OR_NODE:
item->m_eval_res = m_nodes[item->m_left].m_eval_res || m_nodes[item->m_right].m_eval_res;
break;
default:
m_nodes[idx].make_eval(filter_item);
break;
}
nodes_num--;
}
return m_nodes[0].m_eval_res;
}
bool PolicyLogicalTree::get_roles(global_roles_in_use *roles)
{
for (size_t i = 0; i < m_flat_tree.size(); ++i) {
PolicyLogicalNode *item = &m_nodes[m_flat_tree[i]];
if (item->m_type == E_FILTER_ROLE_NODE) {
for (size_t idx = 0; idx < item->m_roles.size(); ++idx) {
roles->insert(item->m_roles[idx]);
}
}
}
return !roles->empty();
}
/* Creates node structure for logical tree */
inline void PolicyLogicalTree::create_node(int *idx, EnodeType type, bool has_operator_NOT)
{
m_nodes.push_back(PolicyLogicalNode(type, has_operator_NOT));
*idx = (m_nodes.size() - 1);
}
/*
* Parses (recursively) polish-notation foramt string into logical tree, we support *(and) +(or) !(not) operation here
* take an example: we change the polish-notation into operator expression to make it clean
* *ip[127.0.0.1]roles[10]: ip && role
* **ip[127.0.0.1]app[javaw]roles[10]: ip && role && app
* *!ip[127.0.0.1]+app[javaw]roles[10]: (!ip) && (app || role)
*/
bool PolicyLogicalTree::parse_logical_expression_impl(const gs_stl::gs_string logical_expr_str, int *offset,
int *idx, Edirection direction)
{
int logical_expr_len = logical_expr_str.size();
bool have_operator_NOT = false;
while (*offset < logical_expr_len) {
/* AND/OR node */
if ((logical_expr_str[*offset] == '*') || (logical_expr_str[*offset] == '+')) {
create_node(idx, ((logical_expr_str[*offset] == '*') ? E_AND_NODE : E_OR_NODE), have_operator_NOT);
PolicyLogicalNode *item = &m_nodes.back();
(*offset)++;
return (parse_logical_expression_impl(logical_expr_str, offset, &item->m_left, E_LEFT) &&
parse_logical_expression_impl(logical_expr_str, offset, &item->m_right, E_RIGHT));
} else if (logical_expr_str[*offset] == '!') { /* NOT operator */
have_operator_NOT = true;
(*offset)++;
} else if (logical_expr_str[*offset] == 'i') { /* IP filter node */
create_node(idx, E_FILTER_IP_NODE, have_operator_NOT);
*offset += 3; /* 3: skip 'ip[' */
have_operator_NOT = false; /* forget that we have met NOT operator */
return parse_values(logical_expr_str, offset, &m_nodes.back());
} else if (logical_expr_str[*offset] == 'r') { /* ROLE filter node */
create_node(idx, E_FILTER_ROLE_NODE, have_operator_NOT);
*offset += 6; /* 6: skip 'roles[' */
have_operator_NOT = false; /* forget that we have met NOT operator */
return parse_values(logical_expr_str, offset, &m_nodes.back());
} else if (logical_expr_str[*offset] == 'a') { /* APPLICATION filter node */
create_node(idx, E_FILTER_APP_NODE, have_operator_NOT);
*offset += 4; /* 4: skip 'app[' */
have_operator_NOT = false; /* forget that we have met NOT operator */
return parse_values(logical_expr_str, offset, &m_nodes.back());
} else {
/* unsupported node or out of stream */
}
}
return false;
}
/* Parses polish-notation format string into logical tree (wrapper around real implementation function) */
bool PolicyLogicalTree::parse_logical_expression(const gs_stl::gs_string logical_expr_str)
{
int offset = 0;
m_flat_tree.clear();
m_nodes.clear();
int idx = 0;
if (logical_expr_str.size() > 0 && parse_logical_expression_impl(logical_expr_str, &offset, &idx, E_LEFT)) {
flatten_tree();
return true;
}
/* in case of error reset tree */
return false;
}
/* Flattens logical tree into vector for later logical evaluation */
void PolicyLogicalTree::flatten_tree()
{
if (m_nodes.size() == 0) {
return;
}
gs_stl::gs_vector<int> nodes_stack;
nodes_stack.push_back(0);
while (nodes_stack.size() > 0) {
int idx = nodes_stack.front();
if (idx < (int)m_nodes.size()) {
PolicyLogicalNode cur_node = m_nodes[idx];
nodes_stack.pop_front();
switch (cur_node.m_type) {
case E_AND_NODE:
case E_OR_NODE:
m_flat_tree.push_back(idx);
nodes_stack.push_back(cur_node.m_left);
nodes_stack.push_back(cur_node.m_right);
break;
case E_FILTER_IP_NODE:
case E_FILTER_ROLE_NODE:
case E_FILTER_APP_NODE:
m_flat_tree.push_back(idx);
break;
default:
Assert(true);
break;
}
}
}
}
/*
* check_apps_intersect
*
* check two apps set have interscetion
*/
bool PolicyLogicalTree::check_apps_intersect(string_sort_vector *apps_first, string_sort_vector *apps_second)
{
if (apps_first == NULL || apps_second == NULL) {
return false;
}
for (size_t i = 0; i < apps_first->size(); ++i) {
if (apps_second->find((*apps_first)[i]) != apps_second->end()) {
return true;
}
}
return false;
}
/*
* check_roles_intersect
*
* check two role set have interscetion
*/
bool PolicyLogicalTree::check_roles_intersect(oid_sort_vector *roles_first, oid_sort_vector *roles_second)
{
if (roles_first == NULL || roles_second == NULL) {
return false;
}
for (size_t i = 0; i < roles_first->size(); ++i) {
if (roles_second->find((*roles_first)[i]) != roles_second->end()) {
return true;
}
}
return false;
}
/*
* has_intersect
*
* check this logical tree has intersect whith logicaltree(arg)
*/
bool PolicyLogicalTree::has_intersect(PolicyLogicalTree *arg)
{
if (m_flat_tree.empty()) {
return true;
}
/* flag intersection for each filter item */
bool is_app_intersect = false;
bool is_ip_intersect = false;
bool is_role_intersect = false;
/* is this logical contains app/ip/role */
bool has_app = false;
bool has_ip = false;
bool has_role = false;
/* arg logical tree vs this logical tree */
bool has_arg_app = false;
bool has_arg_ip = false;
bool has_arg_role = false;
for (size_t idx = 0; idx < m_flat_tree.size(); ++idx) {
PolicyLogicalNode *item = &m_nodes[m_flat_tree[idx]];
/*
* because ',' in logical tree mean 'AND', so there is at least one intersect when all flags are true
* for example: filterA: **ip[xxx.xxx.1.1]app[jdbc]role[dev]
* and filterB: **ip[xxx.xxx.1.2]app[jdbc]role[dev] are no intersection,
* because filterA:only dev using jdbc with ip 123.123.1.1
* filterB:only dev using jdbc with ip 123.123.1.2
* filterA and filterB are different user scenarioes
*/
if (is_app_intersect && is_ip_intersect && is_role_intersect) {
break;
}
/* ignore operator node */
if (item->m_type == E_AND_NODE || item->m_type == E_OR_NODE) {
continue;
}
has_app = has_app || (item->m_type == E_FILTER_APP_NODE);
has_ip = has_ip || (item->m_type == E_FILTER_IP_NODE);
has_role = has_role || (item->m_type == E_FILTER_ROLE_NODE);
for (size_t arg_idx = 0; arg_idx < arg->m_flat_tree.size(); ++arg_idx) {
PolicyLogicalNode *arg_item = &arg->m_nodes[arg->m_flat_tree[arg_idx]];
switch (arg_item->m_type) {
case E_FILTER_APP_NODE:
{
has_arg_app = true;
if (item->m_type == arg_item->m_type && !is_app_intersect) {
/* for now 'NOT' operator is not supported */
is_app_intersect = check_apps_intersect(&item->m_apps, &arg_item->m_apps);
}
}
break;
case E_FILTER_ROLE_NODE:
{
has_arg_role = true;
if (item->m_type == arg_item->m_type && !is_role_intersect) {
/* for now 'NOT' operator is not supported */
is_role_intersect = check_roles_intersect(&item->m_roles, &arg_item->m_roles);
}
}
break;
case E_FILTER_IP_NODE:
{
has_arg_ip = true;
if (item->m_type == arg_item->m_type && !is_ip_intersect) {
/* for now 'NOT' operator is not supported */
is_ip_intersect = item->m_ip_range.is_intersect(&arg_item->m_ip_range);
}
}
break;
/* ignore operator node */
case E_AND_NODE:
case E_OR_NODE:
break;
default:
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("Unknown logical filter node")));
break;
}
}
}
/* if filter not contains means for all scenario
* ip/app/role:
* if one logical contains but the other not contains, they must have intersection
* if both logicals are not contains, they must have intersection
*/
is_app_intersect = !has_app || !has_arg_app || is_app_intersect;
is_ip_intersect = !has_ip || !has_arg_ip || is_ip_intersect;
is_role_intersect = !has_role || !has_arg_role || is_role_intersect;
return is_app_intersect && is_ip_intersect && is_role_intersect;
}