477 lines
19 KiB
C++
477 lines
19 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.
|
|
* ---------------------------------------------------------------------------------------
|
|
*
|
|
* access_audit.cpp
|
|
* Utility functions for auditing logs, including privileges checking, flush logs.
|
|
*
|
|
* IDENTIFICATION
|
|
* src/contrib/security_plugin/access_audit.cpp
|
|
*
|
|
* ---------------------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "access/heapam.h"
|
|
#include "access_audit.h"
|
|
#include "access/sysattr.h"
|
|
#include "catalog/gs_auditing_policy.h"
|
|
#include "catalog/gs_auditing_policy_acc.h"
|
|
#include "catalog/gs_auditing_policy_filter.h"
|
|
#include "catalog/gs_auditing_policy_priv.h"
|
|
#include "catalog/pg_namespace.h"
|
|
#include "commands/user.h"
|
|
#include "workload/gscgroup.h"
|
|
#include "miscadmin.h"
|
|
#include "parser/parse_relation.h"
|
|
#include "gs_mask_policy.h"
|
|
#include "gs_policy_labels.h"
|
|
#include "gs_policy_plugin.h"
|
|
#include "pgaudit.h"
|
|
#include "privileges_audit.h"
|
|
#include "rewrite/prs2lock.h"
|
|
#include "gs_policy/policy_common.h"
|
|
#include "gs_policy/gs_policy_utils.h"
|
|
#include "executor/executor.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/syscache.h"
|
|
#include "gs_policy/gs_vector.h"
|
|
#include "gs_policy/gs_string.h"
|
|
|
|
struct log_item {
|
|
log_item(int f = 0, const char *s = ""):first(f), second(s){}
|
|
bool operator <(const log_item &arg) const
|
|
{
|
|
return false;
|
|
}
|
|
int first; /* event type */
|
|
gs_stl::gs_string second; /* event data */
|
|
};
|
|
using access_event_logs = gs_stl::gs_vector<log_item>;
|
|
static THR_LOCAL access_event_logs *access_logs = NULL;
|
|
|
|
/*
|
|
* Recoed access logs(DDL) for auditing policy. If no logs, create first.
|
|
*/
|
|
void save_access_logs(int type, const char *event)
|
|
{
|
|
if (access_logs == NULL) {
|
|
access_logs = new access_event_logs;
|
|
}
|
|
access_logs->push_back(log_item(type, event));
|
|
}
|
|
|
|
/*
|
|
* Flush the audit logs including ddl & dml into the log system(rsyslog or remote log system) from buffer
|
|
* Note that: we use the audit_result to recognize SQL result info(success or fail) by monitor main process
|
|
*/
|
|
void flush_access_logs(AuditResult audit_result)
|
|
{
|
|
if (access_logs == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* go through logs buffer then flush every log item independenly */
|
|
access_event_logs::iterator it = access_logs->begin();
|
|
access_event_logs::iterator eit = access_logs->end();
|
|
for (; it != eit; ++it) {
|
|
if (it->first == AUDIT_POLICY_EVENT) {
|
|
gs_audit_issue_syslog_message("PGAUDIT", it->second.c_str(), AUDIT_POLICY_EVENT, audit_result);
|
|
} else {
|
|
ereport(DEBUG1, (errmsg("flush_access_logs failed as unsupported audit policy type")));
|
|
}
|
|
}
|
|
|
|
/* safety clear as access_logs is thread local */
|
|
delete access_logs;
|
|
access_logs = NULL;
|
|
}
|
|
|
|
static void get_from_bitmapset(const Bitmapset *columns,
|
|
gs_stl::gs_set<int> *column_pos)
|
|
{
|
|
if (!bms_is_empty(columns)) {
|
|
Bitmapset *tmpset = bms_copy(columns);
|
|
int col = 0;
|
|
while ((col = bms_first_member(tmpset)) >= 0) {
|
|
int real_pos = col + FirstLowInvalidHeapAttributeNumber - 1;
|
|
if (real_pos >= 0) {
|
|
column_pos->insert(real_pos);
|
|
}
|
|
}
|
|
bms_free_ext(tmpset);
|
|
}
|
|
}
|
|
|
|
static void get_access_columns_pos(gs_stl::gs_set<int> *column_pos,
|
|
RangeTblEntry *rte)
|
|
{
|
|
if (rte->insertedCols) {
|
|
get_from_bitmapset(rte->insertedCols, column_pos);
|
|
}
|
|
if (rte->updatedCols) {
|
|
get_from_bitmapset(rte->updatedCols, column_pos);
|
|
}
|
|
if (rte->selectedCols) {
|
|
get_from_bitmapset(rte->selectedCols, column_pos);
|
|
}
|
|
}
|
|
|
|
void flush_policy_result(const policy_result *pol_result, int access_type,
|
|
const char *as_command, const char *access_name)
|
|
{
|
|
if (!pol_result->size()) {
|
|
return;
|
|
}
|
|
char user_name[USERNAME_LEN];
|
|
int rc;
|
|
policy_result::const_iterator it = pol_result->begin();
|
|
policy_result::const_iterator eit = pol_result->end();
|
|
for (; it != eit; ++it) {
|
|
char buff[2048] = {0};
|
|
char session_ip[MAX_IP_LEN] = {0};
|
|
|
|
get_session_ip(session_ip, MAX_IP_LEN);
|
|
const char *acc_name = get_access_name(access_type);
|
|
if (!strcasecmp(acc_name, "NONE") && access_name && strlen(access_name) > 0) {
|
|
acc_name = access_name;
|
|
}
|
|
int printed_size = snprintf_s(buff, sizeof(buff), sizeof(buff) - 1,
|
|
"AUDIT EVENT: user name: [%s], app_name: [%s], client_ip: [%s], access type: [%s",
|
|
GetUserName(user_name, sizeof(user_name)), get_session_app_name(), session_ip,
|
|
acc_name);
|
|
securec_check_ss(printed_size, "\0", "\0");
|
|
if (as_command && strlen(as_command) > 0) {
|
|
rc = snprintf_s(buff + printed_size, sizeof(buff) - printed_size, sizeof(buff) - printed_size - 1,
|
|
" %s], policy id: [%lld]", as_command, *(it->first));
|
|
securec_check_ss(rc, "\0", "\0");
|
|
printed_size += rc;
|
|
} else {
|
|
rc = snprintf_s(buff + printed_size, sizeof(buff) - printed_size, sizeof(buff) - printed_size - 1,
|
|
"], policy id: [%lld]", *(it->first));
|
|
securec_check_ss(rc, "\0", "\0");
|
|
printed_size += rc;
|
|
}
|
|
table_policy_result::const_iterator tit = it->second->begin();
|
|
table_policy_result::const_iterator teit = it->second->end();
|
|
for (; tit != teit; ++tit) {
|
|
int tmp_size = printed_size;
|
|
const AccessPair obj_item = *(tit.first);
|
|
switch (obj_item.second) {
|
|
case O_FUNCTION: {
|
|
rc = snprintf_s(buff + tmp_size, sizeof(buff) - tmp_size, sizeof(buff) - tmp_size - 1,
|
|
", function: [%s]", obj_item.first.c_str());
|
|
securec_check_ss(rc, "\0", "\0");
|
|
tmp_size += rc;
|
|
break;
|
|
}
|
|
case O_VIEW: {
|
|
rc = snprintf_s(buff + tmp_size, sizeof(buff) - tmp_size, sizeof(buff) - tmp_size - 1,
|
|
", view: [%s]", obj_item.first.c_str());
|
|
securec_check_ss(rc, "\0", "\0");
|
|
tmp_size += rc;
|
|
break;
|
|
}
|
|
case O_TABLE: {
|
|
rc = snprintf_s(buff + tmp_size, sizeof(buff) - tmp_size, sizeof(buff) - tmp_size - 1,
|
|
", table: [%s]", obj_item.first.c_str());
|
|
securec_check_ss(rc, "\0", "\0");
|
|
tmp_size += rc;
|
|
break;
|
|
}
|
|
case O_DATABASE: {
|
|
rc = snprintf_s(buff + tmp_size, sizeof(buff) - tmp_size, sizeof(buff) - tmp_size - 1,
|
|
", database: [%s]", obj_item.first.c_str());
|
|
securec_check_ss(rc, "\0", "\0");
|
|
tmp_size += rc;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
if (!tit->second->size()) {
|
|
save_access_logs(AUDIT_POLICY_EVENT, buff);
|
|
continue;
|
|
}
|
|
rc = snprintf_s(buff + tmp_size, sizeof(buff) - tmp_size, sizeof(buff) - tmp_size - 1, ", columns: {");
|
|
securec_check_ss(rc, "\0", "\0");
|
|
tmp_size += rc;
|
|
column_set::iterator cit = tit->second->begin();
|
|
column_set::iterator ceit = tit->second->end();
|
|
for (int i = 0; cit != ceit; ++cit, ++i) {
|
|
rc = snprintf_s(buff + tmp_size, sizeof(buff) - tmp_size, sizeof(buff) - tmp_size - 1, "%s%s",
|
|
(i > 0) ? ", " : "", cit->c_str());
|
|
securec_check_ss(rc, "\0", "\0");
|
|
tmp_size += rc;
|
|
}
|
|
rc = snprintf_s(buff + tmp_size, sizeof(buff) - tmp_size, sizeof(buff) - tmp_size - 1, "}");
|
|
securec_check_ss(rc, "\0", "\0");
|
|
tmp_size += rc;
|
|
save_access_logs(AUDIT_POLICY_EVENT, buff);
|
|
}
|
|
}
|
|
}
|
|
|
|
void check_access_table(const policy_set *policy_ids, const char *name, int access_type, int object_type,
|
|
const char *as_command)
|
|
{
|
|
policy_result pol_result;
|
|
int block_behaviour = 0;
|
|
PolicyLabelItem item("", name, "", object_type);
|
|
PolicyLabelItem view_item(0, 0, O_VIEW);
|
|
check_audit_policy_access(&item, &view_item, access_type, policy_ids, &pol_result,
|
|
get_policy_accesses(), &block_behaviour);
|
|
flush_policy_result(&pol_result, access_type, as_command);
|
|
}
|
|
|
|
void check_access_table(const policy_set *policy_ids, RangeVar *rel, int access_type, int object_type,
|
|
const char *as_command, const char *access_name)
|
|
{
|
|
if (rel == NULL) {
|
|
return;
|
|
}
|
|
PolicyLabelItem item(rel->schemaname, rel->relname, "", object_type);
|
|
PolicyLabelItem view_item(0, 0, O_VIEW);
|
|
policy_result pol_result;
|
|
int block_behaviour = 0;
|
|
check_audit_policy_access(&item, &view_item, access_type, policy_ids, &pol_result,
|
|
get_policy_accesses(), &block_behaviour);
|
|
flush_policy_result(&pol_result, access_type, as_command, access_name);
|
|
}
|
|
|
|
void audit_open_relation(List *list, Var *col_att, PolicyLabelItem *full_column, bool *is_found)
|
|
{
|
|
int relation_pos = col_att->varno ? (col_att->varno - 1) : col_att->varno; /* relation position */
|
|
if (relation_pos >= list_length(list)) {
|
|
return;
|
|
}
|
|
RangeTblEntry *rte = (RangeTblEntry *)list_nth(list, relation_pos);
|
|
if (rte && rte->relid > 0) {
|
|
if (rte->relid > 0) {
|
|
Relation tbl_rel = relation_open(rte->relid, AccessShareLock);
|
|
if (tbl_rel) {
|
|
/* schema */
|
|
if (tbl_rel->rd_rel) {
|
|
full_column->m_schema = tbl_rel->rd_rel->relnamespace;
|
|
}
|
|
relation_close(tbl_rel, AccessShareLock);
|
|
}
|
|
}
|
|
/* subquery in from */
|
|
if (rte->rtekind == RTE_SUBQUERY) {
|
|
if (rte->subquery) {
|
|
audit_open_relation(rte->subquery->rtable, col_att, full_column, is_found);
|
|
}
|
|
} else if (rte->relkind == RELKIND_RELATION && !(*is_found)) {
|
|
int col_num = col_att->varattno ? (col_att->varattno - 1) : col_att->varattno;
|
|
if (col_num >= 0 && rte->eref->colnames && list_length(rte->eref->colnames) > col_num) {
|
|
full_column->set_object(rte->relid, O_COLUMN);
|
|
int rc = snprintf_s(full_column->m_column, sizeof(full_column->m_column),
|
|
sizeof(full_column->m_column) - 1, "%s",
|
|
strVal(list_nth(rte->eref->colnames, col_num)));
|
|
securec_check_ss(rc, "\0", "\0");
|
|
*is_found = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void audit_open_view(RuleLock *rules, Var *col_att, PolicyLabelItem* full_column,
|
|
PolicyLabelItem *view_full_column)
|
|
{
|
|
if (col_att == NULL)
|
|
return;
|
|
List *locks = NIL;
|
|
for (int i = 0; i < rules->numLocks && rules->rules[i]; ++i) {
|
|
RewriteRule *rule = rules->rules[i];
|
|
if (rule->event != CMD_SELECT)
|
|
continue;
|
|
locks = lappend(locks, rule);
|
|
}
|
|
if (locks != NIL) {
|
|
ListCell *item = NULL;
|
|
bool is_found = false;
|
|
foreach (item, locks) {
|
|
RewriteRule *rule = (RewriteRule *)lfirst(item);
|
|
Query *rule_action = (Query*)linitial(rule->actions);
|
|
int column_pos = col_att->varattno ? (col_att->varattno - 1) : col_att->varattno;
|
|
if (column_pos >= 0 && rule_action->targetList && column_pos < list_length(rule_action->targetList)) {
|
|
TargetEntry *old_tle = (TargetEntry *)list_nth(rule_action->targetList, column_pos);
|
|
if (old_tle && old_tle->resno == col_att->varattno) {
|
|
Node *src_expr = (Node*)old_tle->expr;
|
|
Var *var = NULL;
|
|
if (IsA(src_expr, Var)) {
|
|
var = (Var*)src_expr;
|
|
audit_open_relation(rule_action->rtable, var, full_column, &is_found);
|
|
if (view_full_column && old_tle->resname) {
|
|
int rc = snprintf_s(view_full_column->m_column, sizeof(view_full_column->m_column),
|
|
sizeof(view_full_column->m_column) - 1, "%s", old_tle->resname);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (locks != NIL) {
|
|
list_free(locks);
|
|
}
|
|
}
|
|
|
|
static void get_column_from_relation(List *colnames, char *col_name, size_t col_name_size, int col_index)
|
|
{
|
|
int num = 1;
|
|
ListCell *c = NULL;
|
|
foreach(c, colnames) {
|
|
if (col_index == num) {
|
|
int rc = snprintf_s(col_name, col_name_size, col_name_size - 1, "%s", strVal(lfirst(c)));
|
|
securec_check_ss(rc, "\0", "\0");
|
|
break;
|
|
}
|
|
++num;
|
|
}
|
|
}
|
|
|
|
/* Append column name to resouce label */
|
|
static inline void append_column_name(PolicyLabelItem *full_column, int col_num, RangeTblEntry *rte)
|
|
{
|
|
full_column->set_object(rte->relid);
|
|
if (col_num > 0 && rte->eref->colnames && list_length(rte->eref->colnames) > 0) {
|
|
get_column_from_relation(rte->eref->colnames, full_column->m_column, sizeof(full_column->m_column), col_num);
|
|
}
|
|
}
|
|
|
|
void get_fqdn_by_relid(RangeTblEntry *rte, PolicyLabelItem *full_column, Var *col_att,
|
|
PolicyLabelItem *view_full_column)
|
|
{
|
|
/* database */
|
|
if (rte && rte->relid > 0) {
|
|
Relation tbl_rel = relation_open(rte->relid, AccessShareLock);
|
|
if (tbl_rel && tbl_rel->rd_rel) {
|
|
/* schema */
|
|
full_column->m_schema = tbl_rel->rd_rel->relnamespace;
|
|
if (tbl_rel->rd_rules) { /* view */
|
|
audit_open_view(tbl_rel->rd_rules, col_att, full_column, view_full_column);
|
|
if (view_full_column) {
|
|
view_full_column->m_schema = tbl_rel->rd_rel->relnamespace;
|
|
view_full_column->set_object(rte->relid, O_VIEW);
|
|
}
|
|
} else {
|
|
if (full_column->m_obj_type == O_COLUMN) {
|
|
append_column_name(full_column, col_att ? col_att->varattno : 0, rte);
|
|
} else {
|
|
full_column->set_object(rte->relid, O_TABLE);
|
|
}
|
|
}
|
|
relation_close(tbl_rel, AccessShareLock);
|
|
}
|
|
return;
|
|
}
|
|
/* record schema name */
|
|
full_column->m_schema = SchemaNameGetSchemaOid(NULL, true);
|
|
if (rte != NULL && rte->relname) {
|
|
full_column->set_object(rte->relname);
|
|
}
|
|
}
|
|
|
|
bool handle_table_entry(RangeTblEntry *rte, int access_type, const policy_set *policy_ids,
|
|
const policy_set *security_policy_ids,
|
|
policy_result *pol_result)
|
|
{
|
|
if (rte == NULL || rte->relname == NULL) {
|
|
return false;
|
|
}
|
|
PolicyLabelItem item;
|
|
item.m_obj_type = (rte->relkind == RELKIND_VIEW || rte->relkind == RELKIND_CONTQUERY) ? O_VIEW : O_TABLE;
|
|
get_fqdn_by_relid(rte, &item);
|
|
if ((policy_ids->size() || security_policy_ids->size()) && rte->eref) {
|
|
bool check_column = false;
|
|
if (security_policy_ids->size()) {
|
|
if (CheckSecurityAccess_hook != NULL) {
|
|
check_column = CheckSecurityAccess_hook(security_policy_ids, pol_result, &item, &item, access_type,
|
|
false, NULL, 0);
|
|
}
|
|
}
|
|
|
|
int block_behaviour = 0;
|
|
if (check_audit_policy_access(&item, &item, access_type, policy_ids, pol_result, get_policy_accesses(),
|
|
&block_behaviour) && rte->eref->colnames) {
|
|
item.m_obj_type = O_COLUMN;
|
|
gs_stl::gs_set<int> column_pos;
|
|
get_access_columns_pos(&column_pos, rte);
|
|
int ind = 0;
|
|
ListCell *citem = NULL;
|
|
foreach (citem, rte->eref->colnames) {
|
|
if (column_pos.find(ind) != column_pos.end()) {
|
|
const char *cname = strVal(lfirst(citem));
|
|
int rc = snprintf_s(item.m_column, sizeof(item.m_column), sizeof(item.m_column) - 1,
|
|
"%s", cname);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
if (security_policy_ids->size() && check_column) {
|
|
if (CheckSecurityAccess_hook != NULL) {
|
|
CheckSecurityAccess_hook(security_policy_ids, pol_result, &item, &item,
|
|
access_type, true, NULL, 0);
|
|
}
|
|
}
|
|
if (policy_ids->size()) {
|
|
check_audit_policy_access(&item, &item, access_type, policy_ids, pol_result,
|
|
get_policy_accesses(), &block_behaviour);
|
|
}
|
|
}
|
|
++ind;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Hook ExecutorStart to get the query text and basic command type for queries
|
|
* that do not contain a table and so can't be idenitified accurately in
|
|
* ExecutorCheckPerms. walk through all the subqueries and check the tables in access_audit process.
|
|
*/
|
|
void handle_subquery(RangeTblEntry *rte, int commandType, policy_result *pol_result,
|
|
_checked_tables *checked_tables, const policy_set *policy_ids,
|
|
const policy_set *security_policy_ids, int *recursion_deep)
|
|
{
|
|
if (*recursion_deep >= 5) {
|
|
return;
|
|
}
|
|
ListCell *lc = NULL;
|
|
foreach(lc, rte->subquery->rtable) {
|
|
RangeTblEntry *sub_rte = (RangeTblEntry *) lfirst(lc);
|
|
if (sub_rte == NULL)
|
|
break;
|
|
|
|
/* recursive call handle_subquery till find a table object */
|
|
if (sub_rte->rtekind == RTE_SUBQUERY && sub_rte->subquery) {
|
|
handle_subquery(sub_rte, commandType, pol_result, checked_tables, policy_ids,
|
|
security_policy_ids, &(++(*recursion_deep)));
|
|
} else if (sub_rte->relname) {
|
|
/*
|
|
* if the table object has not checked before, then run the handle_table_entry to deal with it
|
|
*/
|
|
if (checked_tables->insert(sub_rte->relname).second) {
|
|
CmdType cmd_type = get_rte_commandtype(rte);
|
|
cmd_type = (cmd_type == CMD_UNKNOWN) ? (CmdType)commandType : cmd_type;
|
|
if (!handle_table_entry(sub_rte, cmd_type, policy_ids,
|
|
security_policy_ids, pol_result))
|
|
continue;
|
|
|
|
flush_policy_result(pol_result, cmd_type);
|
|
}
|
|
}
|
|
}
|
|
}
|