diff --git a/src/backend/distributed/commands/seclabel.c b/src/backend/distributed/commands/seclabel.c index 1d274a05627..30cee56756f 100644 --- a/src/backend/distributed/commands/seclabel.c +++ b/src/backend/distributed/commands/seclabel.c @@ -15,6 +15,7 @@ #include "distributed/commands/utility_hook.h" #include "distributed/coordinator_protocol.h" #include "distributed/deparser.h" +#include "distributed/listutils.h" #include "distributed/log_utils.h" #include "distributed/metadata/distobject.h" #include "distributed/metadata_sync.h" @@ -22,9 +23,9 @@ /* * PostprocessSecLabelStmt prepares the commands that need to be run on all workers to assign - * security labels on distributed objects, currently supporting just Role objects. - * It also ensures that all object dependencies exist on all - * nodes for the object in the SecLabelStmt. + * security labels on distributed objects, currently supporting just Role, Table and Column + * objects. It also ensures that all object dependencies exist on all nodes for the object + * in the SecLabelStmt. */ List * PostprocessSecLabelStmt(Node *node, const char *queryString) @@ -37,12 +38,14 @@ PostprocessSecLabelStmt(Node *node, const char *queryString) SecLabelStmt *secLabelStmt = castNode(SecLabelStmt, node); List *objectAddresses = GetObjectAddressListFromParseTree(node, false, true); - if (!IsAnyObjectDistributed(objectAddresses)) + if (!IsAnyObjectDistributedIgnoreObjectSubId(objectAddresses)) { return NIL; } - if (secLabelStmt->objtype != OBJECT_ROLE) + if (secLabelStmt->objtype != OBJECT_ROLE && + secLabelStmt->objtype != OBJECT_TABLE && + secLabelStmt->objtype != OBJECT_COLUMN) { /* * If we are not in the coordinator, we don't want to interrupt the security @@ -52,7 +55,7 @@ PostprocessSecLabelStmt(Node *node, const char *queryString) if (EnableUnsupportedFeatureMessages && IsCoordinator()) { ereport(NOTICE, (errmsg("not propagating SECURITY LABEL commands whose " - "object type is not role"), + "object type is not role or table or table column"), errhint("Connect to worker nodes directly to manually " "run the same SECURITY LABEL command."))); } @@ -63,13 +66,44 @@ PostprocessSecLabelStmt(Node *node, const char *queryString) EnsurePropagationToCoordinator(); EnsureAllObjectDependenciesExistOnAllNodes(objectAddresses); - const char *secLabelCommands = DeparseTreeNode((Node *) secLabelStmt); + List *commandList = NULL; - List *commandList = list_make3(DISABLE_DDL_PROPAGATION, - (void *) secLabelCommands, - ENABLE_DDL_PROPAGATION); + if (secLabelStmt->objtype == OBJECT_ROLE || + secLabelStmt->objtype == OBJECT_TABLE || + secLabelStmt->objtype == OBJECT_COLUMN) + { + const char *secLabelCommands = DeparseTreeNode((Node *) secLabelStmt); + commandList = list_make3(DISABLE_DDL_PROPAGATION, + (void *) secLabelCommands, + ENABLE_DDL_PROPAGATION); + } + + List *DDLJobs = NodeDDLTaskList(REMOTE_NODES, commandList); + + /* + * If the label is for a table or a column, we need to set the targetObjectAddress + * of the DDLJob to the relationId of the table. This is needed to ensure that + * the search path is correctly set for the remote security label command; it + * needs to be able to resolve the table that the label is being defined on. + */ + if (secLabelStmt->objtype == OBJECT_TABLE || + secLabelStmt->objtype == OBJECT_COLUMN) + { + ObjectAddress *target = NULL; + Oid relationId = InvalidOid; + foreach_ptr(target, objectAddresses) + { + relationId = target->objectId; + } + Assert(relationId != InvalidOid); + DDLJob *ddlJob = NULL; + foreach_ptr(ddlJob, DDLJobs) + { + ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId); + } + } - return NodeDDLTaskList(REMOTE_NODES, commandList); + return DDLJobs; } diff --git a/src/backend/distributed/deparser/deparse_seclabel_stmts.c b/src/backend/distributed/deparser/deparse_seclabel_stmts.c index ffe775b76ee..a3d3d67c257 100644 --- a/src/backend/distributed/deparser/deparse_seclabel_stmts.c +++ b/src/backend/distributed/deparser/deparse_seclabel_stmts.c @@ -10,6 +10,7 @@ #include "postgres.h" +#include "catalog/namespace.h" #include "nodes/parsenodes.h" #include "utils/builtins.h" @@ -54,7 +55,35 @@ AppendSecLabelStmt(StringInfo buf, SecLabelStmt *stmt) { case OBJECT_ROLE: { - appendStringInfo(buf, "ROLE %s ", quote_identifier(strVal(stmt->object))); + char *role_name = strVal(stmt->object); + appendStringInfo(buf, "ROLE %s ", quote_identifier(role_name)); + break; + } + + case OBJECT_TABLE: + { + List *names = (List *) stmt->object; + appendStringInfo(buf, "TABLE %s", quote_identifier(strVal(linitial(names)))); + if (list_length(names) > 1) + { + appendStringInfo(buf, ".%s", quote_identifier(strVal(lsecond(names)))); + } + appendStringInfoString(buf, " "); + break; + } + + case OBJECT_COLUMN: + { + List *names = (List *) stmt->object; + Assert(list_length(names) >= 2); + appendStringInfo(buf, "COLUMN %s.%s", + quote_identifier(strVal(linitial(names))), + quote_identifier(strVal(lsecond(names)))); + if (list_length(names) > 2) + { + appendStringInfo(buf, ".%s", quote_identifier(strVal(lthird(names)))); + } + appendStringInfoString(buf, " "); break; } diff --git a/src/backend/distributed/metadata/distobject.c b/src/backend/distributed/metadata/distobject.c index ff5b2c7a954..98ead876e6a 100644 --- a/src/backend/distributed/metadata/distobject.c +++ b/src/backend/distributed/metadata/distobject.c @@ -566,6 +566,37 @@ IsAnyObjectDistributed(const List *addresses) } +/* + * IsAnyObjectDistributedIgnoreObjectSubId determines if any of the given + * addresses are distributed, using IsObjectDistributed(). It disregards + * the object sub-id field of an address, so this is saved and restored + * before and after each call to IsObjectDistributed(). It is used in + * situations where an address by sub object id is not distributed, but + * the same address by object id is distributed; for example, the address + * of a column of a distributed table. + */ +bool +IsAnyObjectDistributedIgnoreObjectSubId(const List *addresses) +{ + ObjectAddress *address = NULL; + bool isDistributed = false; + foreach_ptr(address, addresses) + { + int32 savedObjectSubId = address->objectSubId; + address->objectSubId = 0; + isDistributed = IsObjectDistributed(address); + address->objectSubId = savedObjectSubId; + + if (isDistributed) + { + break; + } + } + + return isDistributed; +} + + /* * GetDistributedObjectAddressList returns a list of ObjectAddresses that contains all * distributed objects as marked in pg_dist_object diff --git a/src/backend/distributed/operations/node_protocol.c b/src/backend/distributed/operations/node_protocol.c index 52e44bea01c..7ac7d2a07a3 100644 --- a/src/backend/distributed/operations/node_protocol.c +++ b/src/backend/distributed/operations/node_protocol.c @@ -36,6 +36,7 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_index.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_seclabel.h" #include "catalog/pg_type.h" #include "commands/sequence.h" #include "foreign/foreign.h" @@ -57,6 +58,7 @@ #include "distributed/citus_ruleutils.h" #include "distributed/commands.h" #include "distributed/coordinator_protocol.h" +#include "distributed/deparser.h" #include "distributed/listutils.h" #include "distributed/metadata_cache.h" #include "distributed/metadata_sync.h" @@ -83,6 +85,7 @@ static char * CitusCreateAlterColumnarTableSet(char *qualifiedRelationName, const ColumnarOptions *options); static char * GetTableDDLCommandColumnar(void *context); static TableDDLCommand * ColumnarGetTableOptionsDDL(Oid relationId); +static List * CreateSecurityLabelCommands(Oid relationId); /* exports for SQL callable functions */ PG_FUNCTION_INFO_V1(master_get_table_metadata); @@ -665,6 +668,9 @@ GetPreLoadTableCreationCommands(Oid relationId, List *policyCommands = CreatePolicyCommands(relationId); tableDDLEventList = list_concat(tableDDLEventList, policyCommands); + List *securityLabelCommands = CreateSecurityLabelCommands(relationId); + tableDDLEventList = list_concat(tableDDLEventList, securityLabelCommands); + /* revert back to original search_path */ PopEmptySearchPath(saveNestLevel); @@ -833,6 +839,109 @@ GetTableRowLevelSecurityCommands(Oid relationId) } +/* + * CreateSecurityLabelCommands takes in a relationId, and returns the + * list of SECURITY LABEL commands on the relation and on the columns + * of the relation. Precondition: relationId identifies a TABLE + */ +static List * +CreateSecurityLabelCommands(Oid relationId) +{ + List *securityLabelCommands = NIL; + + if (!RegularTable(relationId)) /* should be an Assert ? */ + { + return securityLabelCommands; + } + + Relation pg_seclabel = table_open(SecLabelRelationId, AccessShareLock); + ScanKeyData skey[1]; + ScanKeyInit(&skey[0], Anum_pg_seclabel_objoid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relationId)); + SysScanDesc scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, + true, NULL, 1, &skey[0]); + HeapTuple tuple = NULL; + List *table_name = NIL; + Relation relation = NULL; + TupleDesc tupleDescriptor = NULL; + List *securityLabelStmts = NULL; + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + SecLabelStmt *secLabelStmt = makeNode(SecLabelStmt); + + if (relation == NULL) + { + relation = relation_open(relationId, AccessShareLock); + if (!RelationIsVisible(relationId)) + { + char *nsname = get_namespace_name(RelationGetNamespace(relation)); + table_name = lappend(table_name, makeString(nsname)); + } + char *relname = get_rel_name(relationId); + table_name = lappend(table_name, makeString(relname)); + } + + Datum datumArray[Natts_pg_seclabel]; + bool isNullArray[Natts_pg_seclabel]; + int subObjectId = -1; + + heap_deform_tuple(tuple, RelationGetDescr(pg_seclabel), datumArray, + isNullArray); + subObjectId = DatumGetInt32( + datumArray[Anum_pg_seclabel_objsubid - 1]); + secLabelStmt->provider = TextDatumGetCString( + datumArray[Anum_pg_seclabel_provider - 1]); + secLabelStmt->label = TextDatumGetCString( + datumArray[Anum_pg_seclabel_label - 1]); + + if (subObjectId > 0) + { + /* Its a column; construct the name */ + secLabelStmt->objtype = OBJECT_COLUMN; + List *col_name = list_copy(table_name); + + if (tupleDescriptor == NULL) + { + tupleDescriptor = RelationGetDescr(relation); + } + + Form_pg_attribute attrForm = TupleDescAttr(tupleDescriptor, subObjectId - 1); + char *attributeName = NameStr(attrForm->attname); + col_name = lappend(col_name, makeString(attributeName)); + + secLabelStmt->object = (Node *) col_name; + } + else + { + Assert(subObjectId == 0); + secLabelStmt->objtype = OBJECT_TABLE; + secLabelStmt->object = (Node *) table_name; + } + + securityLabelStmts = lappend(securityLabelStmts, secLabelStmt); + } + + Node *stmt = NULL; + foreach_ptr(stmt, securityLabelStmts) + { + char *secLabelStmtString = DeparseTreeNode(stmt); + TableDDLCommand *secLabelCommand = makeTableDDLCommandString(secLabelStmtString); + securityLabelCommands = lappend(securityLabelCommands, secLabelCommand); + } + + systable_endscan(scan); + table_close(pg_seclabel, AccessShareLock); + + if (relation != NULL) + { + relation_close(relation, AccessShareLock); + } + + return securityLabelCommands; +} + + /* * IndexImpliedByAConstraint is a helper function to be used while scanning * pg_index. It returns true if the index identified by the given indexForm is diff --git a/src/include/distributed/metadata/distobject.h b/src/include/distributed/metadata/distobject.h index e98e6ee8698..778223882c8 100644 --- a/src/include/distributed/metadata/distobject.h +++ b/src/include/distributed/metadata/distobject.h @@ -21,6 +21,7 @@ extern bool ObjectExists(const ObjectAddress *address); extern bool CitusExtensionObject(const ObjectAddress *objectAddress); extern bool IsAnyObjectDistributed(const List *addresses); +extern bool IsAnyObjectDistributedIgnoreObjectSubId(const List *addresses); extern bool ClusterHasDistributedFunctionWithDistArgument(void); extern void MarkObjectDistributed(const ObjectAddress *distAddress); extern void MarkObjectDistributedWithName(const ObjectAddress *distAddress, char *name, diff --git a/src/test/regress/expected/seclabel.out b/src/test/regress/expected/seclabel.out index ca6c6f984d0..b78452742b4 100644 --- a/src/test/regress/expected/seclabel.out +++ b/src/test/regress/expected/seclabel.out @@ -1,8 +1,10 @@ -- -- SECLABEL -- --- Test suite for SECURITY LABEL ON ROLE statements +-- Test suite for SECURITY LABEL statements: +-- SECURITY LABEL ON IS -- +-- Citus can propagate ROLE, TABLE and COLUMN objects -- first we remove one of the worker nodes to be able to test -- citus_add_node later SELECT citus_remove_node('localhost', :worker_2_port); @@ -28,7 +30,8 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORD (2 rows) RESET citus.enable_metadata_sync; --- check that we only support propagating for roles +-- check that we only support propagating for roles, tables and columns; +-- support for VIEW and FUNCTION is not there (yet) SET citus.shard_replication_factor to 1; -- distributed table CREATE TABLE a (a int); @@ -43,22 +46,12 @@ CREATE VIEW v_dist AS SELECT * FROM a; -- distributed function CREATE FUNCTION notice(text) RETURNS void LANGUAGE plpgsql AS $$ BEGIN RAISE NOTICE '%', $1; END; $$; -SECURITY LABEL ON TABLE a IS 'citus_classified'; -NOTICE: not propagating SECURITY LABEL commands whose object type is not role -HINT: Connect to worker nodes directly to manually run the same SECURITY LABEL command. SECURITY LABEL ON FUNCTION notice IS 'citus_unclassified'; -NOTICE: not propagating SECURITY LABEL commands whose object type is not role +NOTICE: not propagating SECURITY LABEL commands whose object type is not role or table or table column HINT: Connect to worker nodes directly to manually run the same SECURITY LABEL command. SECURITY LABEL ON VIEW v_dist IS 'citus_classified'; -NOTICE: not propagating SECURITY LABEL commands whose object type is not role +NOTICE: not propagating SECURITY LABEL commands whose object type is not role or table or table column HINT: Connect to worker nodes directly to manually run the same SECURITY LABEL command. -SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; - node_type | result ---------------------------------------------------------------------- - coordinator | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} - worker_1 | -(2 rows) - SELECT node_type, result FROM get_citus_tests_label_provider_labels('notice(text)') ORDER BY node_type; node_type | result --------------------------------------------------------------------- @@ -74,17 +67,9 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('v_dist') OR (2 rows) \c - - - :worker_1_port -SECURITY LABEL ON TABLE a IS 'citus_classified'; SECURITY LABEL ON FUNCTION notice IS 'citus_unclassified'; SECURITY LABEL ON VIEW v_dist IS 'citus_classified'; \c - - - :master_port -SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; - node_type | result ---------------------------------------------------------------------- - coordinator | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} - worker_1 | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} -(2 rows) - SELECT node_type, result FROM get_citus_tests_label_provider_labels('notice(text)') ORDER BY node_type; node_type | result --------------------------------------------------------------------- @@ -99,10 +84,8 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('v_dist') OR worker_1 | {"label": "citus_classified", "objtype": "view", "provider": "citus '!tests_label_provider"} (2 rows) -DROP TABLE a CASCADE; -NOTICE: drop cascades to view v_dist DROP FUNCTION notice; --- test that SECURITY LABEL statement is actually propagated for ROLES +-- test that SECURITY LABEL statement is actually propagated for ROLES, TABLES and COLUMNS SET citus.log_remote_commands TO on; SET citus.grep_remote_commands = '%SECURITY LABEL%'; -- we have exactly one provider loaded, so we may not include the provider in the command @@ -118,6 +101,41 @@ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx SECURITY LABEL for "citus '!tests_label_provider" ON ROLE "user 2" IS 'citus_classified'; NOTICE: issuing SECURITY LABEL FOR "citus '!tests_label_provider" ON ROLE "user 2" IS 'citus_classified' DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +SECURITY LABEL ON TABLE a IS 'citus_classified'; +NOTICE: issuing SECURITY LABEL ON TABLE a IS 'citus_classified' +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +SECURITY LABEL for "citus '!tests_label_provider" ON COLUMN a.a IS 'citus_classified'; +NOTICE: issuing SECURITY LABEL FOR "citus '!tests_label_provider" ON COLUMN a.a IS 'citus_classified' +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +-- ROLE, TABLE and COLUMN should be propagated to the worker +SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_unclassified", "objtype": "role", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_unclassified", "objtype": "role", "provider": "citus '!tests_label_provider"} +(2 rows) + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_classified", "objtype": "role", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_classified", "objtype": "role", "provider": "citus '!tests_label_provider"} +(2 rows) + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} +(2 rows) + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_classified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_classified", "objtype": "column", "provider": "citus '!tests_label_provider"} +(2 rows) + \c - - - :worker_1_port SET citus.log_remote_commands TO on; SET citus.grep_remote_commands = '%SECURITY LABEL%'; @@ -139,6 +157,23 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORD worker_1 | {"label": "citus_classified", "objtype": "role", "provider": "citus '!tests_label_provider"} (2 rows) +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_classified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_classified", "objtype": "column", "provider": "citus '!tests_label_provider"} +(2 rows) + +SECURITY LABEL for "citus '!tests_label_provider" ON COLUMN a.a IS 'citus ''!unclassified'; +NOTICE: issuing SECURITY LABEL FOR "citus '!tests_label_provider" ON COLUMN a.a IS 'citus ''!unclassified' +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} +(2 rows) + RESET citus.log_remote_commands; SECURITY LABEL for "citus '!tests_label_provider" ON ROLE "user 2" IS 'citus ''!unclassified'; SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') ORDER BY node_type; @@ -150,17 +185,24 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') \c - - - :master_port SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; - node_type | result + node_type | result --------------------------------------------------------------------- coordinator | {"label": "citus_classified", "objtype": "role", "provider": "citus '!tests_label_provider"} worker_1 | {"label": "citus_classified", "objtype": "role", "provider": "citus '!tests_label_provider"} (2 rows) -SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') ORDER BY node_type; - node_type | result +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; + node_type | result --------------------------------------------------------------------- - coordinator | {"label": "citus '!unclassified", "objtype": "role", "provider": "citus '!tests_label_provider"} - worker_1 | {"label": "citus '!unclassified", "objtype": "role", "provider": "citus '!tests_label_provider"} + coordinator | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} +(2 rows) + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} (2 rows) -- add a new node and check that it also propagates the SECURITY LABEL statement to the new node @@ -170,6 +212,8 @@ SELECT 1 FROM citus_add_node('localhost', :worker_2_port); NOTICE: issuing SELECT worker_create_or_alter_role('user1', 'CREATE ROLE user1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT -1 PASSWORD NULL', 'ALTER ROLE user1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT -1 PASSWORD NULL');SECURITY LABEL FOR "citus '!tests_label_provider" ON ROLE user1 IS 'citus_classified' DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing SELECT worker_create_or_alter_role('user 2', 'CREATE ROLE "user 2" NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT -1 PASSWORD NULL', 'ALTER ROLE "user 2" NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT -1 PASSWORD NULL');SECURITY LABEL FOR "citus '!tests_label_provider" ON ROLE "user 2" IS 'citus ''!unclassified' +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +NOTICE: issuing SELECT pg_catalog.worker_drop_sequence_dependency('public.a');;DROP TABLE IF EXISTS public.a CASCADE;CREATE TABLE public.a (a integer) USING heap;ALTER TABLE public.a OWNER TO postgres;SECURITY LABEL FOR "citus '!tests_label_provider" ON TABLE public.a IS 'citus_classified';SECURITY LABEL FOR "citus '!tests_label_provider" ON COLUMN public.a.a IS 'citus ''!unclassified';SELECT worker_create_truncate_trigger('public.a') DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx ?column? --------------------------------------------------------------------- @@ -177,7 +221,7 @@ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx (1 row) SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; - node_type | result + node_type | result --------------------------------------------------------------------- coordinator | {"label": "citus_classified", "objtype": "role", "provider": "citus '!tests_label_provider"} worker_1 | {"label": "citus_classified", "objtype": "role", "provider": "citus '!tests_label_provider"} @@ -192,11 +236,33 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') worker_2 | {"label": "citus '!unclassified", "objtype": "role", "provider": "citus '!tests_label_provider"} (3 rows) +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} + worker_2 | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} +(3 rows) + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_2 | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} +(3 rows) + -- disable the GUC and check that the command is not propagated SET citus.enable_alter_role_propagation TO off; SECURITY LABEL ON ROLE user1 IS 'citus_unclassified'; NOTICE: not propagating SECURITY LABEL commands to other nodes HINT: Connect to other nodes directly to manually assign necessary labels. +SECURITY LABEL ON TABLE a IS 'citus_unclassified'; +NOTICE: not propagating SECURITY LABEL commands to other nodes +HINT: Connect to other nodes directly to manually assign necessary labels. +SECURITY LABEL ON COLUMN a.a IS 'citus_classified'; +NOTICE: not propagating SECURITY LABEL commands to other nodes +HINT: Connect to other nodes directly to manually assign necessary labels. SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; node_type | result --------------------------------------------------------------------- @@ -205,6 +271,22 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORD worker_2 | {"label": "citus_classified", "objtype": "role", "provider": "citus '!tests_label_provider"} (3 rows) +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_unclassified", "objtype": "table", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} + worker_2 | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} +(3 rows) + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_classified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_2 | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} +(3 rows) + \c - - - :worker_2_port SET citus.log_remote_commands TO on; SET citus.grep_remote_commands = '%SECURITY LABEL%'; @@ -212,6 +294,12 @@ SET citus.enable_alter_role_propagation TO off; SECURITY LABEL ON ROLE user1 IS 'citus ''!unclassified'; NOTICE: not propagating SECURITY LABEL commands to other nodes HINT: Connect to other nodes directly to manually assign necessary labels. +SECURITY LABEL ON TABLE a IS 'citus ''!unclassified'; +NOTICE: not propagating SECURITY LABEL commands to other nodes +HINT: Connect to other nodes directly to manually assign necessary labels. +SECURITY LABEL ON COLUMN a.a IS 'citus_unclassified'; +NOTICE: not propagating SECURITY LABEL commands to other nodes +HINT: Connect to other nodes directly to manually assign necessary labels. SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; node_type | result --------------------------------------------------------------------- @@ -220,7 +308,29 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORD worker_2 | {"label": "citus '!unclassified", "objtype": "role", "provider": "citus '!tests_label_provider"} (3 rows) +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_unclassified", "objtype": "table", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus_classified", "objtype": "table", "provider": "citus '!tests_label_provider"} + worker_2 | {"label": "citus '!unclassified", "objtype": "table", "provider": "citus '!tests_label_provider"} +(3 rows) + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + node_type | result +--------------------------------------------------------------------- + coordinator | {"label": "citus_classified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_1 | {"label": "citus '!unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} + worker_2 | {"label": "citus_unclassified", "objtype": "column", "provider": "citus '!tests_label_provider"} +(3 rows) + RESET citus.enable_alter_role_propagation; -- cleanup +DROP TABLE a CASCADE; +NOTICE: drop cascades to view v_dist +ERROR: operation is not allowed on this node +HINT: Connect to the coordinator and run it again. +CONTEXT: SQL statement "SELECT master_remove_distributed_table_metadata_from_workers(v_obj.objid, v_obj.schema_name, v_obj.object_name)" +PL/pgSQL function citus_drop_trigger() line XX at PERFORM RESET citus.log_remote_commands; DROP ROLE user1, "user 2"; diff --git a/src/test/regress/sql/seclabel.sql b/src/test/regress/sql/seclabel.sql index d39e0118392..7d78db223fd 100644 --- a/src/test/regress/sql/seclabel.sql +++ b/src/test/regress/sql/seclabel.sql @@ -1,8 +1,10 @@ -- -- SECLABEL -- --- Test suite for SECURITY LABEL ON ROLE statements +-- Test suite for SECURITY LABEL statements: +-- SECURITY LABEL ON IS -- +-- Citus can propagate ROLE, TABLE and COLUMN objects -- first we remove one of the worker nodes to be able to test -- citus_add_node later @@ -22,7 +24,8 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORD RESET citus.enable_metadata_sync; --- check that we only support propagating for roles +-- check that we only support propagating for roles, tables and columns; +-- support for VIEW and FUNCTION is not there (yet) SET citus.shard_replication_factor to 1; -- distributed table CREATE TABLE a (a int); @@ -33,28 +36,23 @@ CREATE VIEW v_dist AS SELECT * FROM a; CREATE FUNCTION notice(text) RETURNS void LANGUAGE plpgsql AS $$ BEGIN RAISE NOTICE '%', $1; END; $$; -SECURITY LABEL ON TABLE a IS 'citus_classified'; SECURITY LABEL ON FUNCTION notice IS 'citus_unclassified'; SECURITY LABEL ON VIEW v_dist IS 'citus_classified'; -SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; SELECT node_type, result FROM get_citus_tests_label_provider_labels('notice(text)') ORDER BY node_type; SELECT node_type, result FROM get_citus_tests_label_provider_labels('v_dist') ORDER BY node_type; \c - - - :worker_1_port -SECURITY LABEL ON TABLE a IS 'citus_classified'; SECURITY LABEL ON FUNCTION notice IS 'citus_unclassified'; SECURITY LABEL ON VIEW v_dist IS 'citus_classified'; \c - - - :master_port -SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; SELECT node_type, result FROM get_citus_tests_label_provider_labels('notice(text)') ORDER BY node_type; SELECT node_type, result FROM get_citus_tests_label_provider_labels('v_dist') ORDER BY node_type; -DROP TABLE a CASCADE; DROP FUNCTION notice; --- test that SECURITY LABEL statement is actually propagated for ROLES +-- test that SECURITY LABEL statement is actually propagated for ROLES, TABLES and COLUMNS SET citus.log_remote_commands TO on; SET citus.grep_remote_commands = '%SECURITY LABEL%'; @@ -64,6 +62,15 @@ SECURITY LABEL ON ROLE user1 IS NULL; SECURITY LABEL ON ROLE user1 IS 'citus_unclassified'; SECURITY LABEL for "citus '!tests_label_provider" ON ROLE "user 2" IS 'citus_classified'; +SECURITY LABEL ON TABLE a IS 'citus_classified'; +SECURITY LABEL for "citus '!tests_label_provider" ON COLUMN a.a IS 'citus_classified'; + +-- ROLE, TABLE and COLUMN should be propagated to the worker +SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + \c - - - :worker_1_port SET citus.log_remote_commands TO on; SET citus.grep_remote_commands = '%SECURITY LABEL%'; @@ -72,13 +79,84 @@ SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORD SECURITY LABEL for "citus '!tests_label_provider" ON ROLE user1 IS 'citus_classified'; SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; +SECURITY LABEL for "citus '!tests_label_provider" ON COLUMN a.a IS 'citus ''!unclassified'; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + RESET citus.log_remote_commands; SECURITY LABEL for "citus '!tests_label_provider" ON ROLE "user 2" IS 'citus ''!unclassified'; SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') ORDER BY node_type; \c - - - :master_port SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; -SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + +SET citus.shard_replication_factor to 1; + +-- Distributed table with delimited identifiers +CREATE TABLE "Dist T" ("col.1" int); +SELECT create_distributed_table('"Dist T"', 'col.1'); + +SECURITY LABEL ON TABLE "Dist T" IS 'citus_classified'; +SECURITY LABEL ON COLUMN "Dist T"."col.1" IS 'citus_classified'; + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"Dist T"') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"Dist T".col.1') ORDER BY node_type; + +-- Add and Drop column +CREATE TABLE tddl (a1 int, b1 int, c1 int); +SELECT create_distributed_table('tddl', 'c1'); + +ALTER TABLE tddl ADD COLUMN d1 varchar(128); + +-- Security label on tddl.d1 is propagated to all nodes +SECURITY LABEL ON COLUMN tddl.d1 IS 'citus_classified'; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('tddl.d1') ORDER BY node_type; + +-- Drop column d1, security label should be removed from all nodes +ALTER TABLE tddl DROP COLUMN d1; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('tddl.d1') ORDER BY node_type; + +-- Define security labels before distributed table creation +CREATE TABLE tb (a1 int, b1 int, c1 int); +SECURITY LABEL ON TABLE tb IS 'citus_classified'; +-- SECURITY LABEL ON COLUMN tb.a1 IS 'citus_classified'; + +-- TODO: propagation of labels to the worker throws an error. +-- Need to investigate why this is happening; log_remote_commands +-- shows that the CREATE TABLE and ALTER TABLE commands use the +-- same name, but the SECURITY LABEL command errors out. +SET citus.log_remote_commands TO on; +SELECT create_distributed_table('tb', 'a1'); +SET citus.log_remote_commands TO off; + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('tb') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('tb.a1') ORDER BY node_type; + +-- Similar error happens with reference tables, so issue is unrelated +-- to shard names +CREATE TABLE tref (a1 int, b1 int, c1 int); +SECURITY LABEL ON TABLE tref IS 'citus_classified'; +SET citus.log_remote_commands TO on; +SELECT create_reference_table('tref'); +SET citus.log_remote_commands TO off; + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('tref') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('tref.a1') ORDER BY node_type; + +-- Distributed table with delimited identifiers - 2 +CREATE TABLE "Dist T2" ("col one" int); +SELECT create_distributed_table('"Dist T2"', 'col one'); + +SECURITY LABEL ON TABLE "Dist T2" IS 'citus_classified'; +SECURITY LABEL ON COLUMN "Dist T2"."col one" IS 'citus_classified'; + +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"Dist T2"') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"Dist T2".col one') ORDER BY node_type; + +-- TODO: repeat the table and column tests using an explicit schema +-- eg CREATE SCHEMA label_test; SET search_path TO label_test; -- repeat tests -- add a new node and check that it also propagates the SECURITY LABEL statement to the new node SET citus.log_remote_commands TO on; @@ -87,20 +165,46 @@ SELECT 1 FROM citus_add_node('localhost', :worker_2_port); SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; SELECT node_type, result FROM get_citus_tests_label_provider_labels('"user 2"') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"Dist T"') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"Dist T".col.1') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"Dist T2"') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('"Dist T2"."col one"') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('tb') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('tb.a1') ORDER BY node_type; -- disable the GUC and check that the command is not propagated SET citus.enable_alter_role_propagation TO off; SECURITY LABEL ON ROLE user1 IS 'citus_unclassified'; +SECURITY LABEL ON TABLE a IS 'citus_unclassified'; +SECURITY LABEL ON COLUMN a.a IS 'citus_classified'; + SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; \c - - - :worker_2_port SET citus.log_remote_commands TO on; SET citus.grep_remote_commands = '%SECURITY LABEL%'; SET citus.enable_alter_role_propagation TO off; SECURITY LABEL ON ROLE user1 IS 'citus ''!unclassified'; +SECURITY LABEL ON TABLE a IS 'citus ''!unclassified'; +SECURITY LABEL ON COLUMN a.a IS 'citus_unclassified'; + SELECT node_type, result FROM get_citus_tests_label_provider_labels('user1') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a') ORDER BY node_type; +SELECT node_type, result FROM get_citus_tests_label_provider_labels('a.a') ORDER BY node_type; + RESET citus.enable_alter_role_propagation; +\c - - - :master_port -- cleanup +DROP TABLE a CASCADE; +DROP TABLE "Dist T" CASCADE; +DROP TABLE "Dist T2" CASCADE; +DROP TABLE tb CASCADE; +DROP TABLE tref CASCADE; +DROP TABLE tddl CASCADE; RESET citus.log_remote_commands; DROP ROLE user1, "user 2";