Skip to content

Commit

Permalink
Support for fixed server role dbcreator (#65)
Browse files Browse the repository at this point in the history
This role has following privileges with it:
Members of the dbcreator fixed server role can
1. Create database
2. Alter database (if has access to that database, i.e either owner of the database or has mapped user in it)
3. Drop any database.

Engine PR : amazon-aurora/postgresql_modified_for_babelfish#93

Issues Resolved
[BABEL-5115]

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Signed-off-by: ANJU BHARTI <[email protected]>
  • Loading branch information
ANJU BHARTI authored and ANJU BHARTI committed Dec 5, 2024
1 parent dadc1d2 commit 52174d6
Show file tree
Hide file tree
Showing 45 changed files with 2,741 additions and 116 deletions.
45 changes: 27 additions & 18 deletions contrib/babelfishpg_tds/src/backend/tds/tdsutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ static bool handle_dropdb(DropdbStmt *dropdb_stmt);
static char *get_role_name(RoleSpec *role);
char *get_rolespec_name_internal(const RoleSpec *role, bool missing_ok);

static Oid bbf_admin_oid = InvalidOid;

/* Returns OID of bbf_role_admin server role */
static Oid
get_bbf_role_admin_oid(void)
{
if (!OidIsValid(bbf_admin_oid))
bbf_admin_oid = get_role_oid(BABELFISH_ROLE_ADMIN, false);
return bbf_admin_oid;
}


/*
* GetUTF8CodePoint - extract the next Unicode code point from 1..4
* bytes at 'in' in UTF-8 encoding.
Expand Down Expand Up @@ -893,19 +905,15 @@ get_rolespec_name_internal(const RoleSpec *role, bool missing_ok)
static void
check_babelfish_droprole_restrictions(char *role)
{
Oid bbf_role_admin_oid = InvalidOid;

if (MyProcPort->is_tds_conn && sql_dialect == SQL_DIALECT_TSQL)
return;

bbf_role_admin_oid = get_role_oid(BABELFISH_ROLE_ADMIN, false);

/*
* Allow DROP ROLE if current user is bbf_role_admin as we need
* to allow remove_babelfish from PG endpoint. It is safe
* since only superusers can assume this role.
*/
if (bbf_role_admin_oid == GetUserId())
if (get_bbf_role_admin_oid() == GetUserId())
return;

if (is_babelfish_role(role))
Expand Down Expand Up @@ -939,17 +947,21 @@ is_babelfish_role(const char *role)
Oid bbf_master_guest_oid;
Oid bbf_tempdb_guest_oid;
Oid bbf_msdb_guest_oid;
Oid securityadmin_oid;
Oid securityadmin;
Oid dbcreator;

sysadmin_oid = get_role_oid(BABELFISH_SYSADMIN, true); /* missing OK */
role_oid = get_role_oid(role, true); /* missing OK */
securityadmin_oid = get_role_oid(BABELFISH_SECURITYADMIN, true); /* missing OK */
securityadmin = get_role_oid(BABELFISH_SECURITYADMIN, true); /* missing OK */
dbcreator = get_role_oid(BABELFISH_DBCREATOR, true); /* missing OK */

if (!OidIsValid(sysadmin_oid) || !OidIsValid(role_oid))
if (!OidIsValid(sysadmin_oid) || !OidIsValid(role_oid)
|| !OidIsValid(securityadmin) || !OidIsValid(dbcreator))
return false;

if (is_member_of_role(sysadmin_oid, role_oid) ||
is_member_of_role(securityadmin_oid, role_oid) ||
is_member_of_role(securityadmin, role_oid) ||
is_member_of_role(dbcreator, role_oid) ||
pg_strcasecmp(role, BABELFISH_ROLE_ADMIN) == 0) /* check if it is bbf_role_admin */
return true;

Expand Down Expand Up @@ -1206,24 +1218,20 @@ static bool
handle_grant_role(GrantRoleStmt *grant_stmt)
{
ListCell *item;
Oid bbf_role_admin_oid = InvalidOid;
Oid securityadmin_oid = InvalidOid;

if (MyProcPort->is_tds_conn && sql_dialect == SQL_DIALECT_TSQL)
return true;

bbf_role_admin_oid = get_role_oid(BABELFISH_ROLE_ADMIN, false);
securityadmin_oid = get_role_oid(BABELFISH_SECURITYADMIN, false);

/*
* Allow GRANT ROLE if current user is bbf_role_admin as we need
* to allow initialise_babelfish from PG endpoint. It is safe
* since only superusers can assume this role.
*/
if (bbf_role_admin_oid == GetUserId())
if (get_bbf_role_admin_oid() == GetUserId())
return true;

/* Restrict roles to added as a member of bbf_role_admin/securityadmin */
/* Restrict roles to added as a member of BBF default server roles */
foreach(item, grant_stmt->granted_roles)
{
AccessPriv *priv = (AccessPriv *) lfirst(item);
Expand All @@ -1234,18 +1242,19 @@ handle_grant_role(GrantRoleStmt *grant_stmt)
continue;

roleid = get_role_oid(rolename, false);
if (OidIsValid(roleid) && (roleid == bbf_role_admin_oid || roleid == securityadmin_oid))
if (OidIsValid(roleid) && IS_DEFAULT_BBF_SERVER_ROLE(rolename))
check_babelfish_alterrole_restictions(false);
}

/* Restrict grant to/from bbf_role_admin/securityadmin role */
/* Restrict grant to/from bbf_role_admin, securityadmin or dbcreator role */

foreach(item, grant_stmt->grantee_roles)
{
RoleSpec *rolespec = lfirst_node(RoleSpec, item);
Oid roleid;

roleid = get_rolespec_oid(rolespec, false);
if (OidIsValid(roleid) && (roleid == bbf_role_admin_oid || roleid == securityadmin_oid))
if (OidIsValid(roleid) && IS_DEFAULT_BBF_SERVER_ROLE(rolespec->rolename))
check_babelfish_alterrole_restictions(false);
}

Expand Down
7 changes: 7 additions & 0 deletions contrib/babelfishpg_tds/src/include/tds_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ extern ProcessUtility_hook_type next_ProcessUtility;
#define BABELFISH_SYSADMIN "sysadmin"
#define BABELFISH_ROLE_ADMIN "bbf_role_admin"
#define BABELFISH_SECURITYADMIN "securityadmin"
#define BABELFISH_DBCREATOR "dbcreator"

#define IS_DEFAULT_BBF_SERVER_ROLE(rolename) \
((strlen(rolename) == 13 && strncmp(rolename, BABELFISH_SECURITYADMIN, 13) == 0) || \
(strlen(rolename) == 14 && strncmp(rolename, BABELFISH_ROLE_ADMIN, 14) == 0) || \
(strlen(rolename) == 9 && strncmp(rolename, BABELFISH_DBCREATOR, 9) == 0) || \
(strlen(rolename) == 8 && strncmp(rolename, BABELFISH_SYSADMIN, 8) == 0))

/* Functions in backend/tds/tdscomm.c */
extern void TdsSetMessageType(uint8_t msgType);
Expand Down
20 changes: 13 additions & 7 deletions contrib/babelfishpg_tsql/sql/babelfishpg_tsql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2141,16 +2141,18 @@ BEGIN
FROM sys.server_principals
WHERE
pg_catalog.lower(name) = login COLLATE sys.database_default
AND type = 'S'));
AND type IN ('S', 'R')));

IF NOT login_valid THEN
RETURN NULL;

ELSIF role = 'public' COLLATE sys.database_default THEN
RETURN 1;

ELSIF role = 'sysadmin' COLLATE sys.database_default OR role = 'securityadmin' COLLATE sys.database_default THEN
has_role = (pg_has_role(login::TEXT, role::TEXT, 'MEMBER') OR pg_has_role(login::TEXT, 'sysadmin'::TEXT, 'MEMBER'));
ELSIF role COLLATE sys.database_default IN ('sysadmin', 'securityadmin', 'dbcreator') THEN
has_role = (pg_has_role(login::TEXT, role::TEXT, 'MEMBER')
OR ((login COLLATE sys.database_default NOT IN ('sysadmin', 'securityadmin', 'dbcreator'))
AND pg_has_role(login::TEXT, 'sysadmin'::TEXT, 'MEMBER')));
IF has_role THEN
RETURN 1;
ELSE
Expand All @@ -2161,7 +2163,6 @@ BEGIN
'serveradmin',
'setupadmin',
'processadmin',
'dbcreator',
'diskadmin',
'bulkadmin') THEN
RETURN 0;
Expand Down Expand Up @@ -2410,8 +2411,8 @@ BEGIN
OR pg_catalog.lower(rolname) = pg_catalog.lower(PG_CATALOG.RTRIM(@srvrolename)))
AND type = 'R')
OR pg_catalog.lower(PG_CATALOG.RTRIM(@srvrolename)) IN (
'serveradmin', 'setupadmin', 'securityadmin', 'processadmin',
'dbcreator', 'diskadmin', 'bulkadmin')
'serveradmin', 'setupadmin', 'processadmin',
'diskadmin', 'bulkadmin')
BEGIN
SELECT CAST(Ext1.rolname AS sys.SYSNAME) AS 'ServerRole',
CAST(Ext2.rolname AS sys.SYSNAME) AS 'MemberName',
Expand Down Expand Up @@ -2529,7 +2530,12 @@ CAST(0 AS INT) AS serveradmin,
CAST(0 AS INT) AS setupadmin,
CAST(0 AS INT) AS processadmin,
CAST(0 AS INT) AS diskadmin,
CAST(0 AS INT) AS dbcreator,
CAST(
CASE
WHEN is_srvrolemember('dbcreator', Base.name) = 1 THEN 1
ELSE 0
END
AS INT) AS dbcreator,
CAST(0 AS INT) AS bulkadmin
FROM sys.server_principals AS Base
WHERE Base.type in ('S', 'U');
Expand Down
7 changes: 6 additions & 1 deletion contrib/babelfishpg_tsql/sql/ownership.sql
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ CREATE OR REPLACE PROCEDURE initialize_babelfish ( sa_name VARCHAR(128) )
LANGUAGE plpgsql
AS $$
DECLARE
reserved_roles varchar[] := ARRAY['sysadmin', 'securityadmin',
reserved_roles varchar[] := ARRAY['sysadmin', 'securityadmin', 'dbcreator',
'master_dbo', 'master_guest', 'master_db_owner', 'master_db_accessadmin', 'master_db_datareader', 'master_db_datawriter',
'tempdb_dbo', 'tempdb_guest', 'tempdb_db_owner', 'tempdb_db_accessadmin', 'tempdb_db_datareader', 'tempdb_db_datawriter',
'msdb_dbo', 'msdb_guest', 'msdb_db_owner', 'msdb_db_accessadmin', 'msdb_db_datareader', 'msdb_db_datawriter'];
Expand Down Expand Up @@ -290,12 +290,14 @@ BEGIN
END IF;

EXECUTE format('CREATE ROLE securityadmin CREATEROLE INHERIT PASSWORD NULL');
EXECUTE format('CREATE ROLE dbcreator CREATEDB INHERIT PASSWORD NULL');
EXECUTE format('CREATE ROLE bbf_role_admin CREATEDB CREATEROLE INHERIT PASSWORD NULL');
EXECUTE format('GRANT CREATE ON DATABASE %s TO bbf_role_admin WITH GRANT OPTION', CURRENT_DATABASE());
EXECUTE format('GRANT %I to bbf_role_admin WITH ADMIN TRUE;', sa_name);
EXECUTE format('CREATE ROLE sysadmin CREATEDB CREATEROLE INHERIT ROLE %I', sa_name);
EXECUTE format('GRANT sysadmin TO bbf_role_admin WITH ADMIN TRUE');
EXECUTE format('GRANT securityadmin TO bbf_role_admin WITH ADMIN TRUE');
EXECUTE format('GRANT dbcreator TO bbf_role_admin WITH ADMIN TRUE');
EXECUTE format('GRANT USAGE, SELECT ON SEQUENCE sys.babelfish_partition_function_seq TO sysadmin WITH GRANT OPTION');
EXECUTE format('GRANT USAGE, SELECT ON SEQUENCE sys.babelfish_partition_scheme_seq TO sysadmin WITH GRANT OPTION');
EXECUTE format('GRANT USAGE, SELECT ON SEQUENCE sys.babelfish_db_seq TO sysadmin WITH GRANT OPTION');
Expand All @@ -306,6 +308,7 @@ BEGIN
CALL sys.babel_initialize_logins('sysadmin');
CALL sys.babel_initialize_logins('bbf_role_admin');
CALL sys.babel_initialize_logins('securityadmin');
CALL sys.babel_initialize_logins('dbcreator');
CALL sys.babel_create_builtin_dbs(sa_name);
CALL sys.initialize_babel_extras();
-- run analyze for all babelfish catalog
Expand All @@ -329,6 +332,8 @@ BEGIN
DROP ROLE bbf_role_admin;
DROP OWNED BY securityadmin;
DROP ROLE securityadmin;
DROP OWNED BY dbcreator;
DROP ROLE dbcreator;
END
$$;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,22 @@ $$;
DO
LANGUAGE plpgsql
$$
DECLARE securityadmin TEXT;
DECLARE
existing_server_roles TEXT;
BEGIN
IF EXISTS (
SELECT FROM pg_catalog.pg_roles
WHERE rolname = 'securityadmin')
THEN
RAISE EXCEPTION 'Role "securityadmin" already exists.';
SELECT STRING_AGG(rolname::text, ', ')
INTO existing_server_roles FROM pg_catalog.pg_roles
WHERE rolname IN ('securityadmin', 'dbcreator');

IF existing_server_roles IS NOT NULL THEN
RAISE EXCEPTION 'The following role(s) already exist(s): %', existing_server_roles;
ELSE
EXECUTE format('CREATE ROLE securityadmin CREATEROLE INHERIT PASSWORD NULL');
EXECUTE format('GRANT securityadmin TO bbf_role_admin WITH ADMIN TRUE');
CALL sys.babel_initialize_logins('securityadmin');
EXECUTE format('CREATE ROLE dbcreator CREATEDB INHERIT PASSWORD NULL');
EXECUTE format('GRANT dbcreator TO bbf_role_admin WITH ADMIN TRUE');
CALL sys.babel_initialize_logins('dbcreator');
END IF;
END;
$$;
Expand Down Expand Up @@ -433,7 +438,12 @@ CAST(0 AS INT) AS serveradmin,
CAST(0 AS INT) AS setupadmin,
CAST(0 AS INT) AS processadmin,
CAST(0 AS INT) AS diskadmin,
CAST(0 AS INT) AS dbcreator,
CAST(
CASE
WHEN is_srvrolemember('dbcreator', Base.name) = 1 THEN 1
ELSE 0
END
AS INT) AS dbcreator,
CAST(0 AS INT) AS bulkadmin
FROM sys.server_principals AS Base
WHERE Base.type in ('S', 'U');
Expand Down Expand Up @@ -1553,16 +1563,18 @@ BEGIN
FROM sys.server_principals
WHERE
pg_catalog.lower(name) = login COLLATE sys.database_default
AND type = 'S'));
AND type IN ('S', 'R')));

IF NOT login_valid THEN
RETURN NULL;

ELSIF role = 'public' COLLATE sys.database_default THEN
RETURN 1;

ELSIF role = 'sysadmin' COLLATE sys.database_default OR role = 'securityadmin' COLLATE sys.database_default THEN
has_role = (pg_has_role(login::TEXT, role::TEXT, 'MEMBER') OR pg_has_role(login::TEXT, 'sysadmin'::TEXT, 'MEMBER'));
ELSIF role COLLATE sys.database_default IN ('sysadmin', 'securityadmin', 'dbcreator') THEN
has_role = (pg_has_role(login::TEXT, role::TEXT, 'MEMBER')
OR ((login COLLATE sys.database_default NOT IN ('sysadmin', 'securityadmin', 'dbcreator'))
AND pg_has_role(login::TEXT, 'sysadmin'::TEXT, 'MEMBER')));
IF has_role THEN
RETURN 1;
ELSE
Expand All @@ -1573,7 +1585,6 @@ BEGIN
'serveradmin',
'setupadmin',
'processadmin',
'dbcreator',
'diskadmin',
'bulkadmin') THEN
RETURN 0;
Expand Down Expand Up @@ -1708,8 +1719,8 @@ BEGIN
OR pg_catalog.lower(rolname) = pg_catalog.lower(PG_CATALOG.RTRIM(@srvrolename)))
AND type = 'R')
OR pg_catalog.lower(PG_CATALOG.RTRIM(@srvrolename)) IN (
'serveradmin', 'setupadmin', 'securityadmin', 'processadmin',
'dbcreator', 'diskadmin', 'bulkadmin')
'serveradmin', 'setupadmin', 'processadmin',
'diskadmin', 'bulkadmin')
BEGIN
SELECT CAST(Ext1.rolname AS sys.SYSNAME) AS 'ServerRole',
CAST(Ext2.rolname AS sys.SYSNAME) AS 'MemberName',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2072,7 +2072,7 @@ check_server_role_and_throw_if_unsupported (const char *serverrole, int position
errmsg("Fixed server role '%s' is currently not supported in Babelfish", serverrole),
parser_errposition(position)));
}
else if (!IS_ROLENAME_SYSADMIN(serverrole) && !IS_ROLENAME_SECURITYADMIN(serverrole))
else if (!IS_BBF_FIXED_SERVER_ROLE(serverrole))
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Only fixed server role is supported in ALTER SERVER ROLE statement"),
Expand Down
18 changes: 11 additions & 7 deletions contrib/babelfishpg_tsql/src/catalog.c
Original file line number Diff line number Diff line change
Expand Up @@ -4892,6 +4892,17 @@ rename_tsql_db(char *old_db_name, char *new_db_name)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Cannot change the name of the system database %s.", old_db_name)));

/*
* Check permission on the given database.
* Dbcreator can only alter the databases in which it has a mapped user.
*/
if (!has_privs_of_role(GetSessionUserId(), get_sysadmin_oid()) && !(get_user_for_database(old_db_name)
&& has_privs_of_role(GetSessionUserId(), get_dbcreator_oid())))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("User does not have permission to rename the database \'%s\', the database does not exist, or the database is not in a state that allows access checks.",
old_db_name)));

Assert (*pltsql_protocol_plugin_ptr);
/* 50 tries with 100ms sleep between tries makes 5 sec total wait */
for (tries = 0; tries < 50; tries++)
Expand All @@ -4912,13 +4923,6 @@ rename_tsql_db(char *old_db_name, char *new_db_name)
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("The database could not be exclusively locked to perform the operation.")));

/* Check permission on the given database. */
if (!has_privs_of_role(GetSessionUserId(), get_role_oid("sysadmin", false)))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("User does not have permission to rename the database \'%s\', the database does not exist, or the database is not in a state that allows access checks.",
old_db_name)));

/*
* Get an exclusive lock on the logical database we are trying to rename.
*/
Expand Down
11 changes: 11 additions & 0 deletions contrib/babelfishpg_tsql/src/catalog.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ typedef FormData_bbf_function_ext *Form_bbf_function_ext;
#define PUBLIC_ROLE_NAME "public"
#define BABELFISH_SECURITYADMIN "securityadmin"
#define BABELFISH_SYSADMIN "sysadmin"
#define BABELFISH_DBCREATOR "dbcreator"
#define PERMISSIONS_FOR_ALL_OBJECTS_IN_SCHEMA "ALL"
#define ALL_PERMISSIONS_ON_RELATION 47 /* last 6 bits as 101111 represents ALL privileges on a relation. */
#define ALL_PERMISSIONS_ON_FUNCTION 128 /* last 8 bits as 10000000 represents ALL privileges on a procedure/function. */
Expand All @@ -336,6 +337,16 @@ typedef FormData_bbf_function_ext *Form_bbf_function_ext;
(strlen(rolname) == 13 && \
strncmp(rolname, BABELFISH_SECURITYADMIN, 13) == 0)

/* check if rolename is dbcreator */
#define IS_ROLENAME_DBCREATOR(rolname) \
(strlen(rolname) == 9 && \
strncmp(rolname, BABELFISH_DBCREATOR, 9) == 0)

#define IS_BBF_FIXED_SERVER_ROLE(rolename) \
(IS_ROLENAME_SYSADMIN(rolename) || \
IS_ROLENAME_SECURITYADMIN(rolename) || \
IS_ROLENAME_DBCREATOR(rolename))

extern int permissions[];

extern Oid bbf_schema_perms_oid;
Expand Down
Loading

0 comments on commit 52174d6

Please sign in to comment.