Adds datacl propagation

pull/7240/head
gindibay 2023-11-15 16:04:26 +03:00
parent cf019b858c
commit 9a558bdece
9 changed files with 471 additions and 12 deletions

View File

@ -14,10 +14,8 @@
#include "postgres.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_database.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "commands/dbcommands.h"
#include "nodes/parsenodes.h"
#include "tcop/utility.h"

View File

@ -658,6 +658,50 @@ GenerateCreateDatabaseStatementFromPgDatabase(Form_pg_database databaseForm)
return str.data;
}
/*
* GrantOnDatabaseDDLCommands returns a list of sql statements to idempotently apply a
* GRANT on distributed databases.
*/
List * GenerateGrantDatabaseCommandList(void){
List *grantCommands = NIL;
Relation pgDatabaseRel = table_open(DatabaseRelationId, AccessShareLock);
TableScanDesc scan = table_beginscan_catalog(pgDatabaseRel, 0, NULL);
HeapTuple tuple = NULL;
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
Form_pg_database databaseForm = (Form_pg_database) GETSTRUCT(tuple);
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(
NameStr(databaseForm->datname), false);
/* skip databases that are not distributed */
if (!IsAnyObjectDistributed(list_make1(dbAddress)))
{
continue;
}
List *dbGrants = GrantOnDatabaseDDLCommands(databaseForm->oid);
/* append dbGrants into grantCommands*/
grantCommands = list_concat(grantCommands, dbGrants);
}
char *grantCommand = NULL;
foreach_ptr(grantCommand, grantCommands)
{
elog(DEBUG1, "grantCommand: %s", grantCommand);
}
heap_endscan(scan);
table_close(pgDatabaseRel, AccessShareLock);
return grantCommands;
}
/*
* GenerateCreateDatabaseCommandList returns a list of CREATE DATABASE statements
@ -666,8 +710,7 @@ GenerateCreateDatabaseStatementFromPgDatabase(Form_pg_database databaseForm)
* Commands in the list are wrapped by citus_internal_database_command() UDF
* to avoid from transaction block restrictions that apply to database commands
*/
List *
GenerateCreateDatabaseCommandList(void)
List * GenerateCreateDatabaseCommandList(void)
{
List *commands = NIL;

View File

@ -465,6 +465,13 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
List *ownerDDLCommands = DatabaseOwnerDDLCommands(dependency);
databaseDDLCommands = list_concat(databaseDDLCommands, ownerDDLCommands);
}
//TODO: To reviewer: Having a code block for dependency makes sense
// However dependency tree is based on pg metadata; which does not reflect
// actual database dependencies. I added this block just to point out the issue.
// if(EnableCreateDatabasePropagation){
// List *dbGrants = GrantOnDatabaseDDLCommands(dependency->objectId);
// databaseDDLCommands = list_concat(databaseDDLCommands, dbGrants);
// }
return databaseDDLCommands;
}

View File

@ -513,6 +513,8 @@ GenerateRoleOptionsList(HeapTuple tuple)
List *
GenerateCreateOrAlterRoleCommand(Oid roleOid)
{
elog(LOG,"GenerateCreateOrAlterRoleCommand execution");
HeapTuple roleTuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleOid));
Form_pg_authid role = ((Form_pg_authid) GETSTRUCT(roleTuple));

View File

@ -123,6 +123,7 @@ static List * GetObjectsForGrantStmt(ObjectType objectType, Oid objectId);
static AccessPriv * GetAccessPrivObjectForGrantStmt(char *permission);
static List * GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid,
AclItem *aclItem);
static List * GenerateGrantOnDatabaseFromAclItem(Oid databaseOid, AclItem *aclItem);
static List * GenerateGrantOnFunctionQueriesFromAclItem(Oid schemaOid,
AclItem *aclItem);
static List * GrantOnSequenceDDLCommands(Oid sequenceOid);
@ -154,6 +155,7 @@ static char * RemoteSchemaIdExpressionByName(char *schemaName);
static char * RemoteTypeIdExpression(Oid typeId);
static char * RemoteCollationIdExpression(Oid colocationId);
static char * RemoteTableIdExpression(Oid relationId);
static void SendDatabaseGrantSyncCommands(MetadataSyncContext *context);
PG_FUNCTION_INFO_V1(start_metadata_sync_to_all_nodes);
@ -2046,6 +2048,80 @@ GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid, AclItem *aclItem)
return queries;
}
List *
GrantOnDatabaseDDLCommands(Oid databaseOid)
{
HeapTuple databaseTuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(databaseOid));
bool isNull = true;
Datum aclDatum = SysCacheGetAttr(DATABASEOID, databaseTuple, Anum_pg_database_datacl,
&isNull);
if (isNull)
{
ReleaseSysCache(databaseTuple);
return NIL;
}
Acl *acl = DatumGetAclPCopy(aclDatum);
AclItem *aclDat = ACL_DAT(acl);
int aclNum = ACL_NUM(acl);
List *commands = NIL;
ReleaseSysCache(databaseTuple);
for (int i = 0; i < aclNum; i++)
{
commands = list_concat(commands,
GenerateGrantOnDatabaseFromAclItem(
databaseOid,&aclDat[i]));
}
return commands;
}
List *
GenerateGrantOnDatabaseFromAclItem(Oid databaseOid, AclItem *aclItem)
{
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_DATABASE;
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_DATABASE;
/*
* seems unlikely but we check if there is a grant option in the list without the actual permission
*/
Assert(!(grants & ACL_CONNECT) || (permissions & ACL_CONNECT));
Assert(!(grants & ACL_CREATE) || (permissions & ACL_CREATE));
Assert(!(grants & ACL_CREATE_TEMP) || (permissions & ACL_CREATE_TEMP));
Oid granteeOid = aclItem->ai_grantee;
List *queries = NIL;
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
if (permissions & ACL_CONNECT)
{
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
OBJECT_DATABASE ,granteeOid, databaseOid, "CONNECT",
grants & ACL_CONNECT));
queries = lappend(queries, query);
}
if (permissions & ACL_CREATE)
{
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
OBJECT_DATABASE, granteeOid, databaseOid, "CREATE",
grants & ACL_CREATE));
queries = lappend(queries, query);
}
if (permissions & ACL_CREATE_TEMP)
{
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
OBJECT_DATABASE, granteeOid, databaseOid, "TEMPORARY",
grants & ACL_CREATE_TEMP));
queries = lappend(queries, query);
}
queries = lappend(queries, "RESET ROLE");
return queries;
}
/*
* GenerateGrantStmtForRights is the function for creating GrantStmt's for all
@ -2120,6 +2196,11 @@ GetObjectsForGrantStmt(ObjectType objectType, Oid objectId)
return list_make1(sequence);
}
case OBJECT_DATABASE:
{
return list_make1(makeString(get_database_name(objectId)));
}
default:
{
elog(ERROR, "unsupported object type for GRANT");
@ -4563,13 +4644,6 @@ PropagateNodeWideObjectsCommandList(void)
/* collect all commands */
List *ddlCommands = NIL;
if (EnableCreateDatabasePropagation)
{
/* get commands for database creation */
List *createDatabaseCommands = GenerateCreateDatabaseCommandList();
ddlCommands = list_concat(ddlCommands, createDatabaseCommands);
}
if (EnableAlterRoleSetPropagation)
{
/*
@ -4580,6 +4654,13 @@ PropagateNodeWideObjectsCommandList(void)
ddlCommands = list_concat(ddlCommands, alterRoleSetCommands);
}
if (EnableCreateDatabasePropagation)
{
/* get commands for database creation */
List *createDatabaseCommands = GenerateCreateDatabaseCommandList();
ddlCommands = list_concat(ddlCommands, createDatabaseCommands);
}
return ddlCommands;
}
@ -4611,7 +4692,7 @@ SyncDistributedObjects(MetadataSyncContext *context)
Assert(ShouldPropagate());
/* Send systemwide objects, only roles for now */
/* send systemwide objects; i.e. roles and databases for now */
SendNodeWideObjectsSyncCommands(context);
/*
@ -4651,6 +4732,12 @@ SyncDistributedObjects(MetadataSyncContext *context)
* those tables.
*/
SendInterTableRelationshipCommands(context);
/*
* After creation of databases and roles, send the grant database commands
* to the workers.
*/
SendDatabaseGrantSyncCommands(context);
}
@ -4675,6 +4762,29 @@ SendNodeWideObjectsSyncCommands(MetadataSyncContext *context)
SendOrCollectCommandListToActivatedNodes(context, commandList);
}
/*
* SendDatabaseGrantSyncCommands sends database grants to roles to workers with
* transactional or nontransactional mode according to transactionMode inside
* metadataSyncContext.
* This function is called after SendNodeWideObjectsSyncCommands and SendDependencyCreationCommands
* because we need both databases and roles to be created on the worker.
*/
static void
SendDatabaseGrantSyncCommands(MetadataSyncContext *context)
{
/* propagate node wide objects. It includes only roles for now. */
List *commandList = GenerateGrantDatabaseCommandList();
if (commandList == NIL)
{
return;
}
commandList = lcons(DISABLE_DDL_PROPAGATION, commandList);
commandList = lappend(commandList, ENABLE_DDL_PROPAGATION);
SendOrCollectCommandListToActivatedNodes(context, commandList);
}
/*
* SendShellTableDeletionCommands sends sequence, and shell table deletion

View File

@ -244,6 +244,7 @@ extern List * DropDatabaseStmtObjectAddress(Node *node, bool missingOk,
extern List * CreateDatabaseStmtObjectAddress(Node *node, bool missingOk,
bool isPostprocess);
extern List * GenerateCreateDatabaseCommandList(void);
extern List * GenerateGrantDatabaseCommandList(void);
/* domain.c - forward declarations */

View File

@ -107,6 +107,7 @@ extern char * ColocationIdUpdateCommand(Oid relationId, uint32 colocationId);
extern char * CreateSchemaDDLCommand(Oid schemaId);
extern List * GrantOnSchemaDDLCommands(Oid schemaId);
extern List * GrantOnFunctionDDLCommands(Oid functionOid);
extern List * GrantOnDatabaseDDLCommands(Oid databaseOid);
extern List * GrantOnForeignServerDDLCommands(Oid serverId);
extern List * GenerateGrantOnForeignServerQueriesFromAclItem(Oid serverId,
AclItem *aclItem);

View File

@ -487,6 +487,7 @@ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
NOTICE: issuing DROP DATABASE db_force_test WITH ( FORCE )
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
reset citus.log_remote_commands;
reset citus.grep_remote_commands;
SELECT * FROM public.check_database_on_all_nodes('db_force_test') ORDER BY node_type;
node_type | result
---------------------------------------------------------------------
@ -537,6 +538,186 @@ SELECT * FROM public.check_database_on_all_nodes('distributed_db') ORDER BY node
drop database distributed_db;
set citus.enable_create_database_propagation TO off;
drop database non_distributed_db;
-- test role grants on DATABASE in metadata sync
SET citus.enable_create_database_propagation TO on;
CREATE ROLE db_role_grants_test_role_exists_on_node_2;
select 1 from citus_remove_node('localhost', :worker_2_port);
?column?
---------------------------------------------------------------------
1
(1 row)
CREATE DATABASE db_role_grants_test;
revoke connect,temp,temporary on database db_role_grants_test from public;
SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%CREATE ROLE%';
CREATE ROLE db_role_grants_test_role_missing_on_node_2;
NOTICE: issuing SELECT worker_create_or_alter_role('db_role_grants_test_role_missing_on_node_2', 'CREATE ROLE db_role_grants_test_role_missing_on_node_2', 'ALTER ROLE db_role_grants_test_role_missing_on_node_2')
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
RESET citus.log_remote_commands ;
RESET citus.grep_remote_commands;
-- check the privileges before grant
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CREATE')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
f
f
(2 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'TEMPORARY')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
f
f
(2 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CONNECT')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
f
f
(2 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CREATE')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
f
f
(2 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'TEMPORARY')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
f
f
(2 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CONNECT')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
f
f
(2 rows)
SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%GRANT%';
grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test to db_role_grants_test_role_exists_on_node_2;
NOTICE: issuing GRANT connect, temporary, create ON DATABASE db_role_grants_test TO db_role_grants_test_role_exists_on_node_2;
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test to db_role_grants_test_role_missing_on_node_2;
NOTICE: issuing GRANT connect, temporary, create ON DATABASE db_role_grants_test TO db_role_grants_test_role_missing_on_node_2;
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
select 1 from citus_add_node('localhost', :worker_2_port);
NOTICE: issuing CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION pg_database_owner;SET ROLE pg_database_owner;GRANT USAGE ON SCHEMA public TO pg_database_owner;;GRANT CREATE ON SCHEMA public TO pg_database_owner;;RESET ROLE;SET ROLE pg_database_owner;GRANT USAGE ON SCHEMA public TO PUBLIC;;RESET ROLE
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
NOTICE: issuing CREATE SCHEMA IF NOT EXISTS information_schema AUTHORIZATION postgres;SET ROLE postgres;GRANT USAGE ON SCHEMA information_schema TO postgres;;GRANT CREATE ON SCHEMA information_schema TO postgres;;RESET ROLE;SET ROLE postgres;GRANT USAGE ON SCHEMA information_schema TO PUBLIC;;RESET ROLE
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
NOTICE: issuing SET citus.enable_ddl_propagation TO 'off';SET ROLE postgres;GRANT CONNECT ON DATABASE db_role_grants_test TO postgres;;GRANT CREATE ON DATABASE db_role_grants_test TO postgres;;GRANT TEMPORARY ON DATABASE db_role_grants_test TO postgres;;RESET ROLE;SET ROLE postgres;GRANT CONNECT ON DATABASE db_role_grants_test TO db_role_grants_test_role_exists_on_node_2;;GRANT CREATE ON DATABASE db_role_grants_test TO db_role_grants_test_role_exists_on_node_2;;GRANT TEMPORARY ON DATABASE db_role_grants_test TO db_role_grants_test_role_exists_on_node_2;;RESET ROLE;SET ROLE postgres;GRANT CONNECT ON DATABASE db_role_grants_test TO db_role_grants_test_role_missing_on_node_2;;GRANT CREATE ON DATABASE db_role_grants_test TO db_role_grants_test_role_missing_on_node_2;;GRANT TEMPORARY ON DATABASE db_role_grants_test TO db_role_grants_test_role_missing_on_node_2;;RESET ROLE;SET citus.enable_ddl_propagation TO 'on'
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
?column?
---------------------------------------------------------------------
1
(1 row)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CREATE')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'TEMPORARY')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CONNECT')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CREATE')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'TEMPORARY')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CONNECT')
$$
) ORDER BY result;
result
---------------------------------------------------------------------
t
t
t
(3 rows)
DROP DATABASE db_role_grants_test;
DROP ROLE db_role_grants_test_role_exists_on_node_2;
DROP ROLE db_role_grants_test_role_missing_on_node_2;
--clean up resources created by this test
-- DROP TABLESPACE is not supported, so we need to drop it manually.
SELECT result FROM run_command_on_all_nodes(

View File

@ -271,6 +271,7 @@ set citus.grep_remote_commands = '%DROP DATABASE%';
drop database db_force_test with (force);
reset citus.log_remote_commands;
reset citus.grep_remote_commands;
SELECT * FROM public.check_database_on_all_nodes('db_force_test') ORDER BY node_type;
@ -296,6 +297,121 @@ set citus.enable_create_database_propagation TO off;
drop database non_distributed_db;
-- test role grants on DATABASE in metadata sync
SET citus.enable_create_database_propagation TO on;
CREATE ROLE db_role_grants_test_role_exists_on_node_2;
select 1 from citus_remove_node('localhost', :worker_2_port);
CREATE DATABASE db_role_grants_test;
revoke connect,temp,temporary on database db_role_grants_test from public;
SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%CREATE ROLE%';
CREATE ROLE db_role_grants_test_role_missing_on_node_2;
RESET citus.log_remote_commands ;
RESET citus.grep_remote_commands;
-- check the privileges before grant
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CREATE')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'TEMPORARY')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CONNECT')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CREATE')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'TEMPORARY')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CONNECT')
$$
) ORDER BY result;
SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%GRANT%';
grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test to db_role_grants_test_role_exists_on_node_2;
grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test to db_role_grants_test_role_missing_on_node_2;
select 1 from citus_add_node('localhost', :worker_2_port);
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CREATE')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'TEMPORARY')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CONNECT')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CREATE')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'TEMPORARY')
$$
) ORDER BY result;
SELECT result from run_command_on_all_nodes(
$$
select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CONNECT')
$$
) ORDER BY result;
DROP DATABASE db_role_grants_test;
DROP ROLE db_role_grants_test_role_exists_on_node_2;
DROP ROLE db_role_grants_test_role_missing_on_node_2;
--clean up resources created by this test
-- DROP TABLESPACE is not supported, so we need to drop it manually.