Das Manage­ment der Auto­ri­sie­rung von Usern spielt für IT-Sys­teme eine zen­trale Bedeu­tung. Denn nur so ist sicher­ge­stellt, dass die Nut­zer einer Platt­form genau den Zugriff auf die Daten erhal­ten, der sinn­voll erscheint. Dabei sollte die Berech­ti­gungs­ver­gabe aus fach­li­cher Sicht sinn­voll und aus recht­li­cher Sicht legal sein. Eine Her­aus­for­de­rung für Betrei­ber von ana­ly­ti­schen Platt­for­men mit einer viel­fäl­ti­gen Palette an Tools, Daten­ban­ken und damit ver­bun­de­nen Sys­te­men ist, die Berech­ti­gungs­ver­gabe für die User an einer Stelle zen­tral und effek­tiv zu gestal­ten, um Trans­pa­renz und betrieb­li­che Ver­wal­tung zu ermöglichen.

In die­sem Bei­trag liegt der Fokus auf den tech­ni­schen Mög­lich­kei­ten, mit denen man die­ses Vor­ha­ben umset­zen kann. Der Fokus liegt hier­bei auf Appli­ka­tio­nen und Daten­ban­ken, die mit­tels SQL über eine zen­trale Daten­bank zum Spei­chern der Meta­da­ten ver­wal­tet wer­den kön­nen. Ein klei­ner Exkurs in die Umset­zung für Appli­ka­tion mit einer REST API als Schnitt­stelle wird hier auch bespro­chen. Zunächst wird dar­auf ein­ge­gan­gen, wie ein sol­ches Kon­strukt an Meta­da­ten auf einer rela­tio­na­len Daten­bank auf­ge­baut wer­den kann.

Auf­bau der Meta­da­ten zur Berechtigungsvergabe

Viele popu­läre Sys­teme und Daten­ban­ken nut­zen für die Auto­ri­sie­rung ihrer User das Prin­zip von RBAC (Role Based Access Con­trol), so zum Bei­spiel auch Snow­flake. Dem User gleich­ge­stellt sind in RBAC-Daten­ban­ken Grup­pen und Rol­len. Ent­spre­chend der im zu ver­wal­ten­den Sys­tem genutz­ten Enti­tä­ten wer­den dafür Tabel­len in den Meta­da­ten ange­legt. Eine sol­che Tabelle, bspw. für die ver­füg­ba­ren Rol­len kann so aussehen:

ROLE_NAMEINSERT_TSINSERT_USERACTIVE_FLAG
ops_admins2023-11-12 14:10:08.000user11
ops_developers2023-11-22 16:08:01.129user21
user_fachbereich12023-12-02 09:05:07.385user21
user_fachbereich22023-12-03 10:10:09.499user20
Tabelle 1: Meta­da­ten für Rollen

Die Tabelle beinhal­tet den Namen der Rolle (ROLE_NAME), den Zeit­punkt (Timestamp), zu dem die­ser Ein­trag in die Tabelle getä­tigt wurde (INSERT_TS) und eine Mar­kie­rung, ob der Ein­trag gül­tig ist (ACTIVE_FLAG). Nicht mehr gül­tige Ein­träge wer­den bei Ver­bin­dung mit den übri­gen Meta­da­ten nicht wei­ter berück­sich­tigt, bspw. über Fil­ter in SQL-Statements.

Ver­bin­dung der Metadaten

Im nächs­ten Schritt erfolgt eine Ver­knüp­fung der Meta­da­ten für die Enti­tä­ten Rolle, Schema und Tabelle. Die Tabel­len der ein­zel­nen Enti­tä­ten soll­ten dabei im Opti­mal­fall Pri­mary Keys besit­zen. Im Fall von Tabelle 1 besteht der Pri­mary Key nur aus der ROLE_NAME. Die Ver­knüp­fung der Enti­tä­ten erfolgt dann in geson­der­ten Tabel­len. Nach­fol­gend ein Bei­spiel für die Bezie­hung Rolle zu Datenbankschema:

ROLE_NAMEDATABASE_NAMESCHEMA_NAMEPRIVILEGE_TYPEINSERT_TSINSERT_USERLAST_UPDATE_TSLAST_UPDATE_USERACTIVE_FLAG
user_fachbereich1database1schema1USAGE2023-12-03 14:15:18.111user12024-01-15 08:10:11.859user11
user_fachbereich1database1schema2USAGE2023-12-03 14:15:18.111user12024-01-15 08:10:11.861user21
user_fachbereich2database2schema1CREATE2023-12-03 14:15:18.111user22024-01-15 08:10:11.870user21
Tabelle 2: Meta­da­ten für die Bezie­hung Rolle zu Datenbank-Schema

Auch bei Tabelle 2 sollte es einen Pri­mary Key geben, in die­sem Fall auf die Spal­ten ROLE_NAME, DATABASE_NAME und SCHEMA_NAME. Die Dar­stel­lung in Tabelle 1 ist dabei für eine Umge­bung nutz­bar, die sich in ver­schie­dene Daten­ban­ken und darin befind­li­che Sche­mata auf­tei­len lässt. Dies ist bspw. bei Snow­flake der Fall. Sollte es nur eine Daten­bank auf der Umge­bung geben, so kann die Spalte DATABASE_NAME weg­ge­las­sen werden.

Die­ses Prin­zip lässt sich auf alle für das jewei­lige Sys­tem not­wen­di­gen Enti­tä­ten über­tra­gen. Die­ses Kon­strukt kann dabei auf einer rela­tio­na­len Daten­bank der Wahl imple­men­tiert wer­den, je nach ver­füg­ba­rer Infrastruktur des Betrei­bers auf OnPre­mise oder einer eta­blier­ten Cloud-Umgebung.

Ver­knüp­fung mit den Meta­da­ten der Applikationen

Übli­cher­weise kön­nen SQL-Befehle auf einer rela­tio­na­len Daten­bank nur aus­ge­führt wer­den, wenn sich darin neben dem Ver­mei­den von Syn­tax-Feh­lern auch keine inhalt­li­chen Wider­sprü­che mit den Meta­da­ten der Daten­bank fin­den las­sen. Mit den Meta­da­ten der Appli­ka­tio­nen sind hier z.B. Infor­ma­tion Sche­mas auf der Snow­flake gemeint. 

Bei dem bis­her beschrie­be­nen Kon­strukt an Meta­da­ten zur Berech­ti­gungs­ver­gabe kann es jedoch z.B. auch vor­kom­men, dass Berech­ti­gun­gen für Tabel­len umge­setzt wer­den, die noch nicht ange­legt wor­den sind. Um dies zu ver­mei­den, sollte eine Ver­knüp­fung der Meta­da­ten der Daten­bank mit den kon­stru­ier­ten Meta­da­ten in Betracht gezo­gen werden.

Eine Mög­lich­keit bie­tet sich hier­bei durch Views. Hier ein Bei­spiel, wie man eine sol­che View auf­bauen kann:

CREATE OR REPLACE VIEW compare_permissions AS
WITH tobegranted_permissions AS (
/*
Abgleich zu vergebende Berechtigungen mit Liste an Tabellen, für die das Vergeben möglich ist (da diese existieren)
*/
SELECT permissions.*
FROM permissions -- Tabelle mit Berechtigungen, die noch zu vergeben sind
JOIN available_tables -- Tabelle aus den Metadaten der Datenbank mit den verfügbaren Tabellen, auf die berechtigt werden kann
ON (available_tables.database_name = permissions.database_name)
AND (available_tables.schema_name = permissions.schema_name)
AND (available_tables.table_name = permissions.table_name)
),
existing_permissions AS (
/*
Abgleich bereits vergebene Berechtigungen mit Liste an Tabellen, für die das Entfernen der Berechtigungen möglich ist
*/
SELECT active_permissions.*
FROM active_permissions -- Tabelle mit den bereits eingerichteten Berechtigungen
JOIN available_tables -- siehe oben
ON (available_tables.database_name = active_permissions.database_name)
AND (available_tables.schema_name = active_permissions.schema_name)
AND (available_tables.table_name = active_permissions.table_name)
)
comparing_view AS (
/*
Hier werden die beiden obigen Tabellen abgeglichen, es entsteht eine Tabelle mit NULL-Werten für entweder die Spalten mit *tobegranted oder *existing
*/
SELECT
tobegranted_permissions.database_name AS database_name_tobegranted,
tobegranted_permissions.schema_name AS schema_name_tobegranted,
tobegranted_permissions.table_name AS table_name_tobegranted,
tobegranted_permissions.role_name AS role_name_tobegranted,
existing_permissions.database_name AS database_name_existing,
existing_permissions.schema_name AS schema_name_existing,
existing_permissions.table_name AS table_name_existing,
existing_permissions.role_name AS role_name_existing
FROM tobegranted_permissions
FULL OUTER JOIN existing_permissions
ON (tobegranted_permissions.database_name = existing_permissions.database_name)
AND (tobegranted_permissions.schema_name = existing_permissions.schema_name)
AND (tobegranted_permissions.table_name = existing_permissions.table_name)
AND (tobegranted_permissions.role_name = existing_permissions.role_name)
)
/*
Im letzten Schritt werden die Datensätze aus dem Abgleich entfernt, wo Soll und Ist übereinstimmen
*/
SELECT *
FROM comparing_view
WHERE (role_name_tobegranted IS NULL OR role_name_existing IS NULL);

Aus die­ser View las­sen sich dann SQL-Befehle ablei­ten, mit denen sich auf einer rela­tio­nen Daten­bank Berech­ti­gun­gen ver­ge­ben und wie­der ent­zie­hen las­sen. Die Befehle las­sen sich im Stil der fol­gen­den Bei­spiele gene­rie­ren. Diese kön­nen sich dann in einer Tabelle spei­chern und his­to­ri­sie­ren lassen.

SELECT 'GRANT SELECT ON ' || compare_permissions.schema_name_tobegranted || '.' || compare_permissions.table_name_tobegranted || ' TO ROLE ' || compare_permissions.role_name_tobegranted || ';' FROM compare_permissions;

SELECT 'REVOKE SELECT ON ' || compare_permissions.schema_name_existing || '.' || compare_permissions.table_name_existing || ' FROM ROLE ' || compare_permissions.role_name_existing || ';' FROM compare_permissions;

Diese State­ments las­sen sich auch für das Manage­ment der Zuge­hö­rig­keit von Usern zu Grup­pen und Rol­len nut­zen. Eine Tabelle, aus der sich die State­ments ablei­ten las­sen, kann so aussehen:

ROLE_NAMEUSER_NAMEINSERT_TSINSERT_USERLAST_UPDATE_TSLAST_UPDATE_USERACTIVE_FLAG
user_fachbereich1user12024-01-15 10:10:11.457user12024-01-15 11:23:58.591user11
user_fachbereich1user22024-01-15 10:10:11.458user12024-01-15 11:23:58.599user20
Tabelle 3: Meta­da­ten für die Bezie­hung Rolle zu User (Name der Tabelle: ROLE_USER)

Die SQL-Befehle zur Gene­rie­rung von REVOKE- und GRANT-State­ments lau­ten dann

SELECT 'REVOKE ROLE ' || ROLE_NAME || ' FROM USER ' || USER_NAME || ';' FROM ROLE_USER WHERE ACTIVE_FLAG = 0;

SELECT 'GRANT ROLE' || ROLE_NAME || ' TO USER ' || USER_NAME || ';' FROM ROLE_USER WHERE ACTIVE_FLAG = 1;

Betrieb der Metadaten

Das Kern­stück bei der Ver­wal­tung des auf­ge­bau­ten Meta­da­ten-Kon­strukt sind, wie bei einer rela­tio­na­len Daten­bank üblich, SQL-Befehle. Hier kön­nen ver­schie­dene Ansätze gewählt wer­den: So kön­nen die Befehle auf der Daten­bank direkt abge­setzt wer­den. Snow­flake bie­tet dabei bspw. die Mög­lich­keit, Tasks zu ver­wen­den, um die Com­mands zu sche­du­len. Diese Option ist opti­mal, wenn es nur eine ein­zige zugrun­de­lie­gende Daten­bank­tech­no­lo­gie in der zu ver­wal­ten­den Umge­bung gibt.

Gibt es mehr als eine Daten­bank oder Appli­ka­tion auf der zu ver­wal­ten­den Umge­bung, so kann der Ein­satz eines auf der Platt­form bereits eta­blier­ten Tools sinn­voll sein. Zur Daten­über­tra­gung eig­nen sich Tools wie Tal­end oder dbt. Vor­aus­set­zung hier­bei ist, dass das Tool Con­nec­to­ren für jede zu ver­wal­tende Daten­bank und Appli­ka­tion anbie­tet. Dies ist auf einer bereits eta­blier­ten Platt­form zumeist gege­ben. Bei einer noch auf­zu­set­zen­den Umge­bung sollte dies berück­sich­tigt wer­den. Alter­na­tiv kann die Berech­ti­gungs­ver­gabe auch über eigens dafür geschrie­bene Skripte umge­setzt wer­den. Diese soll­ten in einer von der Platt­form unter­stütz­ten Spra­che geschrie­ben werden.

Berech­ti­gungs­ver­gabe über eine REST API

Auf einer ana­ly­ti­schen Platt­form kann es auch Appli­ka­tio­nen geben, die ihre Auto­ri­sie­rung nicht über SQL-Befehle steu­ern las­sen. Ein Fall sind Web­ap­pli­ka­tio­nen, die eine REST API für sol­che Zwe­cke ver­wen­den. Ein Bei­spiel wäre hier die Anwen­dung Apa­che Ran­ger, die das Frame­work für das Manage­ment von Regeln (Poli­cies) für den Daten­zu­griff auf Hadoop-Platt­for­men bereitstellt.

Apa­che Ran­ger ver­wen­det eine REST API als Schnitt­stelle, um eine auto­ma­ti­sierte Ver­wal­tung der soge­nann­ten Poli­cies zu ermög­li­chen. Ein ent­spre­chen­des JSON, das als Input für Apa­che Ran­ger dient, für die Tabelle TABLE_NAME in Data­base DATABASE_NAME für die Gruppe GROUP_NAME in Apa­che Hive (Data Ware­house Sys­tem für Hadoop-Platt­for­men) kann dann so aussehen:

template_policy = {'isEnabled': True, 'service' : 'Hive', 'name': '', 'policytype': 0, 'isAuditEnabled': True, 'resources': {}, 'policyItems': [], 'zoneName': ''}

# Security Zone in Apache Ranger
template_policy['zoneName'] = securityZone

template_policy['name'] = str(DATABASE_NAME) + '|' + str(TABLE_NAME)

template_policy['resources'] = {
{'database': {'values': [DATABASE_NAME], 'isExcludes': False, 'isRecursive': False},
'table': {'values': [TABLE_NAME], 'isExcludes': False, 'isRecursive': False},
'column': {'values': ['*'], 'isExcludes': False, 'isRecursive': False}
}

template_policy['policyItems'] = [{
'accesses': [{'type': 'select', 'isAllowed': True}, {'type': 'read', 'isAllowed': True}],
'users': [], 
'groups': [GROUP_NAME],
'conditions' : [],
'delegateAdmins': False
}]

In die­sem Fall wird die Berech­ti­gung auf die Gruppe GROUP_NAME ver­ge­ben. Die Werte DATABASE_NAME, TABLE_NAME und GROUP_NAME wer­den aus einer Tabelle aus den Meta­da­ten ent­nom­men, die gemäß dem oben beschrie­be­nen Kon­strukt diese Enti­tä­ten mit­ein­an­der verbindet.

Fazit

Das orga­ni­sierte Manage­ment der Berech­ti­gungs­ver­gabe auf einer ana­ly­ti­schen Platt­form stellt Ent­wick­ler und Admi­nis­tra­to­ren der Platt­form vor eine Reihe von Her­aus­for­de­run­gen. In die­sem Bei­trag wurde ein Kon­strukt an Meta­da­ten prä­sen­tiert, mit dem sich eine zen­trale Daten­bank für die Meta­da­ten rund um die Auto­ri­sie­rung von Usern, Grup­pen und Rol­len für ver­schie­dene Appli­ka­tio­nen der Platt­form umset­zen lassen. 

Das vor­ge­stellte Kon­strukt und des­sen Umset­zung kon­zen­triert sich dabei auf Appli­ka­tio­nen mit einem ver­füg­ba­ren SQL-Kon­text. Mit die­sem kön­nen GRANT- und REVOKE-State­ments ein­ge­setzt wer­den. Es bie­tet aber durch Ein­satz von dafür gebau­ten Skrip­ten und ETL-Jobs auch die Mög­lich­keit, das Kon­strukt auch für Appli­ka­tio­nen ohne SQL-Kon­text umzu­set­zen. Ein Bei­spiel haben wir hier mit Apa­che Ran­ger gegeben. 

Per­spek­ti­visch las­sen sich die hier vor­ge­stell­ten Ideen und Ansätze fle­xi­bel erwei­tern. So kann man die­sen auch auf andere Appli­ka­tio­nen mit Schnitt­stel­len abseits von JDBC Con­nec­to­ren und REST API über­tra­gen. Auch kön­nen neben den hier ver­wen­de­ten Enti­tä­ten Rolle, Gruppe und User auch noch wei­tere Enti­tä­ten zur Berech­ti­gungs­ver­gabe ein­ge­führt und gema­nagt werden.