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 tanscorpio7 committed Nov 5, 2024
1 parent b124d15 commit 7992779
Show file tree
Hide file tree
Showing 44 changed files with 2,791 additions and 117 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
22 changes: 14 additions & 8 deletions contrib/babelfishpg_tsql/sql/babelfishpg_tsql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2049,22 +2049,24 @@ DECLARE login_valid BOOLEAN;
BEGIN
role := TRIM(trailing from LOWER(role));
login := TRIM(trailing from LOWER(login));

login_valid = (login = suser_name() COLLATE sys.database_default) OR
(EXISTS (SELECT name
FROM sys.server_principals
WHERE
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 @@ -2075,7 +2077,6 @@ BEGIN
'serveradmin',
'setupadmin',
'processadmin',
'dbcreator',
'diskadmin',
'bulkadmin') THEN
RETURN 0;
Expand Down Expand Up @@ -2324,8 +2325,8 @@ BEGIN
OR lower(rolname) = lower(RTRIM(@srvrolename)))
AND type = 'R')
OR lower(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 @@ -2442,7 +2443,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 @@ -82,17 +82,22 @@ DROP PROCEDURE sys.create_db_roles_during_upgrade();
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 @@ -183,22 +188,24 @@ DECLARE login_valid BOOLEAN;
BEGIN
role := TRIM(trailing from LOWER(role));
login := TRIM(trailing from LOWER(login));

login_valid = (login = suser_name() COLLATE sys.database_default) OR
(EXISTS (SELECT name
FROM sys.server_principals
WHERE
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 @@ -209,20 +216,69 @@ BEGIN
'serveradmin',
'setupadmin',
'processadmin',
'dbcreator',
'diskadmin',
'bulkadmin') THEN
RETURN 0;

ELSE
RETURN NULL;
END IF;
END IF;

EXCEPTION WHEN OTHERS THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;

-- sp_helpsrvrolemember
CREATE OR REPLACE PROCEDURE sys.sp_helpsrvrolemember("@srvrolename" sys.SYSNAME = NULL) AS
$$
BEGIN
-- If server role is not specified, return info for all server roles
IF @srvrolename IS NULL
BEGIN
SELECT CAST(Ext1.rolname AS sys.SYSNAME) AS 'ServerRole',
CAST(Ext2.rolname AS sys.SYSNAME) AS 'MemberName',
CAST(CAST(Base2.oid AS INT) AS sys.VARBINARY(85)) AS 'MemberSID'
FROM pg_catalog.pg_auth_members AS Authmbr
INNER JOIN pg_catalog.pg_roles AS Base1 ON Base1.oid = Authmbr.roleid
INNER JOIN pg_catalog.pg_roles AS Base2 ON Base2.oid = Authmbr.member
INNER JOIN sys.babelfish_authid_login_ext AS Ext1 ON Base1.rolname = Ext1.rolname
INNER JOIN sys.babelfish_authid_login_ext AS Ext2 ON Base2.rolname = Ext2.rolname
WHERE Ext1.type = 'R' AND Ext2.type != 'Z'
ORDER BY ServerRole, MemberName;
END
-- If a valid server role is specified, return its member info
-- If the role is a SQL server predefined role (i.e. serveradmin),
-- do not raise an error even if it does not exist
ELSE IF EXISTS (SELECT 1
FROM sys.babelfish_authid_login_ext
WHERE (rolname = RTRIM(@srvrolename)
OR lower(rolname) = lower(RTRIM(@srvrolename)))
AND type = 'R')
OR lower(RTRIM(@srvrolename)) IN (
'serveradmin', 'setupadmin', 'processadmin',
'diskadmin', 'bulkadmin')
BEGIN
SELECT CAST(Ext1.rolname AS sys.SYSNAME) AS 'ServerRole',
CAST(Ext2.rolname AS sys.SYSNAME) AS 'MemberName',
CAST(CAST(Base2.oid AS INT) AS sys.VARBINARY(85)) AS 'MemberSID'
FROM pg_catalog.pg_auth_members AS Authmbr
INNER JOIN pg_catalog.pg_roles AS Base1 ON Base1.oid = Authmbr.roleid
INNER JOIN pg_catalog.pg_roles AS Base2 ON Base2.oid = Authmbr.member
INNER JOIN sys.babelfish_authid_login_ext AS Ext1 ON Base1.rolname = Ext1.rolname
INNER JOIN sys.babelfish_authid_login_ext AS Ext2 ON Base2.rolname = Ext2.rolname
WHERE Ext1.type = 'R' AND Ext2.type != 'Z'
AND (Ext1.rolname = RTRIM(@srvrolename) OR lower(Ext1.rolname) = lower(RTRIM(@srvrolename)))
ORDER BY ServerRole, MemberName;
END
-- If the specified server role is not valid
ELSE
RAISERROR('%s is not a known fixed role.', 16, 1, @srvrolename);
END;
$$
LANGUAGE 'pltsql';
GRANT EXECUTE ON PROCEDURE sys.sp_helpsrvrolemember TO PUBLIC;

-- SYSLOGINS
CREATE OR REPLACE VIEW sys.syslogins
AS SELECT
Expand Down Expand Up @@ -277,7 +333,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
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
Loading

0 comments on commit 7992779

Please sign in to comment.