Skip to content

Commit

Permalink
Support Securityadmin fixed server role (#67)
Browse files Browse the repository at this point in the history
Added support for new fixed server role securityadmin. This role has following privileges with it:

Members of the securityadmin fixed server role can manage logins and their properties.
They can GRANT, DENY, and REVOKE server-level permissions.
securityadmin can also GRANT, DENY, and REVOKE database-level permissions if they have access to a database.

Original PR with all the comments babelfish-for-postgresql#2907

Issue resolved: BABEL-5040
Signed-off-by: ANJU BHARTI <[email protected]>
  • Loading branch information
anju15bharti authored and ANJU BHARTI committed Dec 5, 2024
1 parent f63e348 commit c46b7da
Show file tree
Hide file tree
Showing 42 changed files with 3,286 additions and 98 deletions.
13 changes: 9 additions & 4 deletions contrib/babelfishpg_tds/src/backend/tds/tdsutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -939,14 +939,17 @@ is_babelfish_role(const char *role)
Oid bbf_master_guest_oid;
Oid bbf_tempdb_guest_oid;
Oid bbf_msdb_guest_oid;
Oid securityadmin_oid;

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 */

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

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

Expand Down Expand Up @@ -1204,11 +1207,13 @@ 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
Expand All @@ -1218,7 +1223,7 @@ handle_grant_role(GrantRoleStmt *grant_stmt)
if (bbf_role_admin_oid == GetUserId())
return true;

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

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

/* Restrict grant to/from bbf_role_admin role */
/* Restrict grant to/from bbf_role_admin/securityadmin 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)
if (OidIsValid(roleid) && (roleid == bbf_role_admin_oid || roleid == securityadmin_oid))
check_babelfish_alterrole_restictions(false);
}

Expand Down
1 change: 1 addition & 0 deletions contrib/babelfishpg_tds/src/include/tds_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ extern ProcessUtility_hook_type next_ProcessUtility;
#define PUBLIC_ROLE_NAME "public"
#define BABELFISH_SYSADMIN "sysadmin"
#define BABELFISH_ROLE_ADMIN "bbf_role_admin"
#define BABELFISH_SECURITYADMIN "securityadmin"

/* Functions in backend/tds/tdscomm.c */
extern void TdsSetMessageType(uint8_t msgType);
Expand Down
13 changes: 8 additions & 5 deletions contrib/babelfishpg_tsql/sql/babelfishpg_tsql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2146,8 +2146,8 @@ BEGIN
ELSIF role = 'public' COLLATE sys.database_default THEN
RETURN 1;

ELSIF role = 'sysadmin' COLLATE sys.database_default THEN
has_role = pg_has_role(login::TEXT, role::TEXT, 'MEMBER');
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'));
IF has_role THEN
RETURN 1;
ELSE
Expand All @@ -2156,9 +2156,7 @@ BEGIN

ELSIF role COLLATE sys.database_default IN (
'serveradmin',
'securityadmin',
'setupadmin',
'securityadmin',
'processadmin',
'dbcreator',
'diskadmin',
Expand Down Expand Up @@ -2513,7 +2511,12 @@ CAST(
ELSE 0
END
AS INT) AS sysadmin,
CAST(0 AS INT) AS securityadmin,
CAST(
CASE
WHEN is_srvrolemember('securityadmin', Base.name) = 1 THEN 1
ELSE 0
END
AS INT) AS securityadmin,
CAST(0 AS INT) AS serveradmin,
CAST(0 AS INT) AS setupadmin,
CAST(0 AS INT) AS processadmin,
Expand Down
14 changes: 10 additions & 4 deletions 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', 'master_dbo', 'master_guest', 'master_db_owner', 'tempdb_dbo', 'tempdb_guest', 'tempdb_db_owner', 'msdb_dbo', 'msdb_guest', 'msdb_db_owner'];
reserved_roles varchar[] := ARRAY['sysadmin', 'securityadmin', 'master_dbo', 'master_guest', 'master_db_owner', 'tempdb_dbo', 'tempdb_guest', 'tempdb_db_owner', 'msdb_dbo', 'msdb_guest', 'msdb_db_owner'];
user_id oid := -1;
db_name name := NULL;
role_name varchar;
Expand All @@ -285,11 +285,13 @@ BEGIN
RAISE E'Could not initialize babelfish with given role name: % is not the DB owner of current database.', sa_name;
END IF;

EXECUTE format('CREATE ROLE securityadmin CREATEROLE 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 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 @@ -299,6 +301,7 @@ BEGIN
CALL sys.babel_initialize_logins(sa_name);
CALL sys.babel_initialize_logins('sysadmin');
CALL sys.babel_initialize_logins('bbf_role_admin');
CALL sys.babel_initialize_logins('securityadmin');
CALL sys.babel_create_builtin_dbs(sa_name);
CALL sys.initialize_babel_extras();
-- run analyze for all babelfish catalog
Expand All @@ -320,6 +323,8 @@ BEGIN
DROP ROLE sysadmin;
DROP OWNED BY bbf_role_admin;
DROP ROLE bbf_role_admin;
DROP OWNED BY securityadmin;
DROP ROLE securityadmin;
END
$$;

Expand Down Expand Up @@ -365,7 +370,8 @@ CAST(CASE WHEN Ext.type = 'R' THEN NULL ELSE Ext.credential_id END AS INT) AS cr
CAST(CASE WHEN Ext.type = 'R' THEN 1 ELSE Ext.owning_principal_id END AS INT) AS owning_principal_id,
CAST(CASE WHEN Ext.type = 'R' THEN 1 ELSE Ext.is_fixed_role END AS sys.BIT) AS is_fixed_role
FROM pg_catalog.pg_roles AS Base INNER JOIN sys.babelfish_authid_login_ext AS Ext ON Base.rolname = Ext.rolname
WHERE (pg_has_role(suser_id(), 'sysadmin'::TEXT, 'MEMBER')
WHERE (pg_has_role(suser_id(), 'sysadmin'::TEXT, 'MEMBER')
OR pg_has_role(suser_id(), 'securityadmin'::TEXT, 'MEMBER')
OR Ext.orig_loginname = suser_name()
OR Ext.orig_loginname = (SELECT pg_get_userbyid(datdba) FROM pg_database WHERE datname = CURRENT_DATABASE()) COLLATE sys.database_default
OR Ext.type = 'R')
Expand Down Expand Up @@ -501,8 +507,8 @@ CAST(Ext.orig_loginname AS sys.nvarchar(128)) AS name,
CAST('SERVER ROLE' AS sys.nvarchar(128)) AS type,
CAST ('GRANT OR DENY' as sys.nvarchar(128)) as usage
FROM pg_catalog.pg_roles AS Base INNER JOIN sys.babelfish_authid_login_ext AS Ext ON Base.rolname = Ext.rolname
WHERE Ext.type = 'R' AND
(pg_has_role(sys.suser_id(), 'sysadmin'::TEXT, 'MEMBER'));
WHERE Ext.type = 'R'
AND bbf_is_member_of_role_nosuper(sys.suser_id(), Base.oid);

GRANT SELECT ON sys.login_token TO PUBLIC;

Expand Down
4 changes: 4 additions & 0 deletions contrib/babelfishpg_tsql/sql/sys_functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4529,6 +4529,10 @@ $$
$$
LANGUAGE SQL STRICT STABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION sys.bbf_is_member_of_role_nosuper(OID, OID)
RETURNS BOOLEAN AS 'babelfishpg_tsql', 'bbf_is_member_of_role_nosuper'
LANGUAGE C STABLE STRICT PARALLEL SAFE;

CREATE OR REPLACE FUNCTION sys.replace (input_string sys.VARCHAR, pattern sys.VARCHAR, replacement sys.VARCHAR)
RETURNS sys.VARCHAR AS
$BODY$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,163 @@ BEGIN
END;
$$;

DO
LANGUAGE plpgsql
$$
DECLARE securityadmin TEXT;
BEGIN
IF EXISTS (
SELECT FROM pg_catalog.pg_roles
WHERE rolname = 'securityadmin')
THEN
RAISE EXCEPTION 'Role "securityadmin" already exists.';
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');
END IF;
END;
$$;

CREATE OR REPLACE FUNCTION sys.bbf_is_member_of_role_nosuper(OID, OID)
RETURNS BOOLEAN AS 'babelfishpg_tsql', 'bbf_is_member_of_role_nosuper'
LANGUAGE C STABLE STRICT PARALLEL SAFE;

-- SERVER_PRINCIPALS
CREATE OR REPLACE VIEW sys.server_principals
AS SELECT
CAST(Ext.orig_loginname AS sys.SYSNAME) AS name,
CAST(Base.oid As INT) AS principal_id,
CAST(CAST(Base.oid as INT) as sys.varbinary(85)) AS sid,
CAST(Ext.type AS CHAR(1)) as type,
CAST(
CASE
WHEN Ext.type = 'S' THEN 'SQL_LOGIN'
WHEN Ext.type = 'R' THEN 'SERVER_ROLE'
WHEN Ext.type = 'U' THEN 'WINDOWS_LOGIN'
ELSE NULL
END
AS NVARCHAR(60)) AS type_desc,
CAST(Ext.is_disabled AS INT) AS is_disabled,
CAST(Ext.create_date AS SYS.DATETIME) AS create_date,
CAST(Ext.modify_date AS SYS.DATETIME) AS modify_date,
CAST(CASE WHEN Ext.type = 'R' THEN NULL ELSE Ext.default_database_name END AS SYS.SYSNAME) AS default_database_name,
CAST(Ext.default_language_name AS SYS.SYSNAME) AS default_language_name,
CAST(CASE WHEN Ext.type = 'R' THEN NULL ELSE Ext.credential_id END AS INT) AS credential_id,
CAST(CASE WHEN Ext.type = 'R' THEN 1 ELSE Ext.owning_principal_id END AS INT) AS owning_principal_id,
CAST(CASE WHEN Ext.type = 'R' THEN 1 ELSE Ext.is_fixed_role END AS sys.BIT) AS is_fixed_role
FROM pg_catalog.pg_roles AS Base INNER JOIN sys.babelfish_authid_login_ext AS Ext ON Base.rolname = Ext.rolname
WHERE (pg_has_role(suser_id(), 'sysadmin'::TEXT, 'MEMBER')
OR pg_has_role(suser_id(), 'securityadmin'::TEXT, 'MEMBER')
OR Ext.orig_loginname = suser_name()
OR Ext.orig_loginname = (SELECT pg_get_userbyid(datdba) FROM pg_database WHERE datname = CURRENT_DATABASE()) COLLATE sys.database_default
OR Ext.type = 'R')
AND Ext.type != 'Z'
UNION ALL
SELECT
CAST('public' AS SYS.SYSNAME) AS name,
CAST(-1 AS INT) AS principal_id,
CAST(CAST(0 as INT) as sys.varbinary(85)) AS sid,
CAST('R' AS CHAR(1)) as type,
CAST('SERVER_ROLE' AS NVARCHAR(60)) AS type_desc,
CAST(0 AS INT) AS is_disabled,
CAST(NULL AS SYS.DATETIME) AS create_date,
CAST(NULL AS SYS.DATETIME) AS modify_date,
CAST(NULL AS SYS.SYSNAME) AS default_database_name,
CAST(NULL AS SYS.SYSNAME) AS default_language_name,
CAST(NULL AS INT) AS credential_id,
CAST(1 AS INT) AS owning_principal_id,
CAST(0 AS sys.BIT) AS is_fixed_role;

GRANT SELECT ON sys.server_principals TO PUBLIC;

-- login_token
CREATE OR REPLACE VIEW sys.login_token
AS SELECT
CAST(Base.oid As INT) AS principal_id,
CAST(CAST(Base.oid as INT) as sys.varbinary(85)) AS sid,
CAST(Ext.orig_loginname AS sys.nvarchar(128)) AS name,
CAST(CASE
WHEN Ext.type = 'U' THEN 'WINDOWS LOGIN'
ELSE 'SQL LOGIN' END AS SYS.NVARCHAR(128)) AS TYPE,
CAST('GRANT OR DENY' as sys.nvarchar(128)) as usage
FROM pg_catalog.pg_roles AS Base INNER JOIN sys.babelfish_authid_login_ext AS Ext ON Base.rolname = Ext.rolname
WHERE Ext.orig_loginname = sys.suser_name()
AND Ext.type in ('S','U')
UNION ALL
SELECT
CAST(Base.oid As INT) AS principal_id,
CAST(CAST(Base.oid as INT) as sys.varbinary(85)) AS sid,
CAST(Ext.orig_loginname AS sys.nvarchar(128)) AS name,
CAST('SERVER ROLE' AS sys.nvarchar(128)) AS type,
CAST ('GRANT OR DENY' as sys.nvarchar(128)) as usage
FROM pg_catalog.pg_roles AS Base INNER JOIN sys.babelfish_authid_login_ext AS Ext ON Base.rolname = Ext.rolname
WHERE Ext.type = 'R'
AND bbf_is_member_of_role_nosuper(sys.suser_id(), Base.oid);

GRANT SELECT ON sys.login_token TO PUBLIC;

-- SYSLOGINS
CREATE OR REPLACE VIEW sys.syslogins
AS SELECT
Base.sid AS sid,
CAST(9 AS SYS.TINYINT) AS status,
Base.create_date AS createdate,
Base.modify_date AS updatedate,
Base.create_date AS accdate,
CAST(0 AS INT) AS totcpu,
CAST(0 AS INT) AS totio,
CAST(0 AS INT) AS spacelimit,
CAST(0 AS INT) AS timelimit,
CAST(0 AS INT) AS resultlimit,
Base.name AS name,
Base.default_database_name AS dbname,
Base.default_language_name AS default_language_name,
CAST(Base.name AS SYS.NVARCHAR(128)) AS loginname,
CAST(NULL AS SYS.NVARCHAR(128)) AS password,
CAST(0 AS INT) AS denylogin,
CAST(1 AS INT) AS hasaccess,
CAST(
CASE
WHEN Base.type_desc = 'WINDOWS_LOGIN' OR Base.type_desc = 'WINDOWS_GROUP' THEN 1
ELSE 0
END
AS INT) AS isntname,
CAST(
CASE
WHEN Base.type_desc = 'WINDOWS_GROUP' THEN 1
ELSE 0
END
AS INT) AS isntgroup,
CAST(
CASE
WHEN Base.type_desc = 'WINDOWS_LOGIN' THEN 1
ELSE 0
END
AS INT) AS isntuser,
CAST(
CASE
WHEN is_srvrolemember('sysadmin', Base.name) = 1 THEN 1
ELSE 0
END
AS INT) AS sysadmin,
CAST(
CASE
WHEN is_srvrolemember('securityadmin', Base.name) = 1 THEN 1
ELSE 0
END
AS INT) AS securityadmin,
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(0 AS INT) AS bulkadmin
FROM sys.server_principals AS Base
WHERE Base.type in ('S', 'U');

GRANT SELECT ON sys.syslogins TO PUBLIC;


/* Helper function to update local variables dynamically during execution */
CREATE OR REPLACE FUNCTION sys.pltsql_assign_var(dno INT, val ANYELEMENT)
Expand Down Expand Up @@ -1137,8 +1294,8 @@ BEGIN
ELSIF role = 'public' COLLATE sys.database_default THEN
RETURN 1;

ELSIF role = 'sysadmin' COLLATE sys.database_default THEN
has_role = pg_has_role(login::TEXT, role::TEXT, 'MEMBER');
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'));
IF has_role THEN
RETURN 1;
ELSE
Expand All @@ -1147,9 +1304,7 @@ BEGIN

ELSIF role COLLATE sys.database_default IN (
'serveradmin',
'securityadmin',
'setupadmin',
'securityadmin',
'processadmin',
'dbcreator',
'diskadmin',
Expand Down
21 changes: 21 additions & 0 deletions contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c
Original file line number Diff line number Diff line change
Expand Up @@ -2058,3 +2058,24 @@ tsql_index_nulls_order(List *indexParams, const char *accessMethod)
}
}
}

static void
check_server_role_and_throw_if_unsupported (const char *serverrole, int position, core_yyscan_t yyscanner)
{
if (strcmp(serverrole, "serveradmin") == 0
|| strcmp(serverrole, "setupadmin") == 0
|| strcmp(serverrole, "processadmin") == 0
|| strcmp(serverrole, "diskadmin") == 0
|| strcmp(serverrole, "bulkadmin") == 0)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
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))
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Only fixed server role is supported in ALTER SERVER ROLE statement"),
parser_errposition(position)));
}
}
Loading

0 comments on commit c46b7da

Please sign in to comment.