Row-Level Security, Dynamic Data Masking & Session Context

SQL Server 2016 erweitert Sicherheitskonzept
Kommentare

SQL Server 2016 bietet einige neue Sicherheitsfeatures, die, richtig angewandt und kombiniert, neue Möglichkeiten eröffnen, um sensible Daten feingranularer und einfacher zu schützen. Dieser Artikel zeigt, wie Row-Level Security, Dynamic Data Masking und Session Context Hand in Hand arbeiten können, um dieses Ziel zu erreichen.

Dieser Text ist auf Basis der Community Technology Preview (CTP) 3.2 geschrieben, die seit Oktober 2015 von Microsoft zum Download bereitgestellt wird. Es besteht also die Möglichkeit, dass die gezeigten Beispiele im finalen Produkt nicht 1:1 funktionieren werden. Die Konzepte und Vorgehensweisen werden sich allerdings nicht mehr grundlegend ändern – der richtige Zeitpunkt also für einen genaueren Blick auf die neuen Möglichkeiten. Der hier gezeigte Ansatz zum Schutz sensibler Daten stützt sich auf reine „SQL-Server-Technik“, um die Sicherheit möglichst eng an die Daten zu binden.

Damit ist es unerheblich, ob eine oder mehrere Anwendungen auf die Daten zugreifen oder ob eine Auswertung und Analyse der Daten stattfindet – es greifen immer die gleichen Mechanismen.

Die hier entwickelte Idee konzentriert sich auf Zugriffssicherheit: Wer darf welche Daten und in welcher Form sehen. Themen wie Back-up-Sicherheit, Transparent Data Encryption (TDE) oder das ebenfalls mit SQL Server 2016 neue Always Encrypted werden nicht benötigt.

SQL-Server-Sicherheit

Zunächst einmal jedoch eine Entwarnung für bestehende Anwendungen. SQL Server 2016 schaltet kein bestehendes Sicherheitsfeature ab oder verändert es signifikant. Viel mehr, und darauf bezieht sich dieser Artikel, werden neue Möglichkeiten geschaffen, die die SQL-Server-Sicherheit erweitern und ergänzen.

Die Mitspieler

Die Mitspieler sind drei neue, bzw. verbesserte Funktionalitäten, die SQL Server 2016 bieten wird: Row-Level Security, Dynamic Data Masking und Session Context. Im Zusammenspiel, und in Verbindung mit dem etablierten Sicherheitssystem des Servers, ergibt sich ein sehr mächtiges und sehr feingranulares Sicherheitskonzept, das sich sowohl für Anwendungen als auch Auswertungs-(analyse-)systeme eignet. Die Zugriffstechnologie ist dabei ebenfalls flexibel wählbar. ADO.NET, ODBC, JDBC, Entity Framework 6.x (eigentlich ja auch nur ADO.NET) etc. sind möglich.

Das Spielfeld

Um die neuen Möglichkeiten zu demonstrieren, benötigt es eine Tabelle, die Details zu Mitarbeitern speichert. Diese Details sind unterschiedlich sensibel. So ist der Name z. B. relativ unkritisch, während auf der anderen Seite das Gehalt streng vertraulich behandelt werden muss und zumindest im Klartext nur von bestimmten Personen eingesehen werden darf. Außerdem soll das Feld „SecurityDescriptor“ festlegen, von wem (in diesem Fall von welchem User oder welcher Rolle) die einzelne Zeile verwendet werden darf. Im Verlauf dieses Artikels kommt dann noch eine Spalte hinzu, die einen Mandantenfilter realisiert (Listing 1).

Und um es möglichst einfach und die Skripte dabei übersichtlich zu halten, ist nur von dieser Tabelle die Rede. Selbstredend ist, dass zu einer relationalen Datenbank meistens mehr als nur eine Tabelle gehört, aber alle gezeigten Gedanken lassen sich beliebig vervielfältigen.

CREATE TABLE [dbo].[Employees]
(
  [ID] [int] PRIMARY KEY CLUSTERED IDENTITY(1,1) NOT NULL,
  [Name] [varchar](100) NULL,
  [Address] [varchar](256) NULL,
  [Email] [varchar](256) NULL,
  [Salary] [decimal](18,2) NOT NULL,
  [SecurityDescriptor] [varchar](128) NULL
);

Da nicht der generelle Zugriff auf diese Tabelle eingeschränkt werden soll, was natürlich zusätzlich möglich ist, werden zu dem auch die entsprechenden Berechtigungen benötigt:

GRANT SELECT, INSERT, DELETE, UPDATE ON [dbo].[Employees] TO PUBLIC;

Besonders das SELECT-Recht spielt eine wichtige Rolle, da es so mindestens zu keinem Fehler beim Lesen kommt, auch wenn der Inhalt aus keiner einzigen Zeile besteht oder nicht in Klartext erscheint.

Row-Level Security

SQL Server 2016 bietet mit Row-Level Security eine eingebaute Funktionalität, die es erlaubt, mittels einer eigenen Funktion zu bestimmen, welche Zeilen einer Tabelle geliefert werden sollen.

Sie besteht aus so genannten „Security Policies“ die jeweils eine oder mehrere „Security Predicates“ umfassen. Ein solches „Security Predicate“, in T-SQL Statements als „Filter Predicates“ bezeichnet, wendet eine Inline Table-Valued Function für eine Tabelle an, die damit bei jedem Zugriff gefiltert wird. Eine Security Policy kann somit für eine oder mehrere Tabellen definiert werden. Standardmäßig wird der Filter auf Leseoperationen angewandt. Zusätzlich können „Block Policies“ dafür verwendet werden, dass auch Data-Manipulation-Language-(DML-)Anweisungen blockiert werden, wenn diese nicht zu dem Filter (also der Table-Valued Function) passen. In einem solchen Fall wird beim Versuch ein Fehler ausgelöst.

Die Funktion wird dann pro in Frage kommender Tabellenzeile aufgerufen und entscheidet, ob diese verwendet werden darf oder nicht. Es findet also ein Filtern statt, und das Thema Performance muss an dieser Stelle definitiv im Auge behalten werden. Pro Datenbank kann es wiederum beliebig viele Policies geben, die einzeln auf Wunsch aktiviert oder deaktiviert werden können.

Listing 2 zeigt zunächst einmal eine Funktion, die als Filter verwendet werden kann. Wichtig ist hier der Zusatz SCHEMABINDING, der zwingend vorhanden sein muss.

-- Funktion für Sicherheitsfilter erstellen
CREATE FUNCTION dbo.fn_AccessFilter(@RoleOrUsername AS sysname)
    RETURNS TABLE
    WITH SCHEMABINDING -- Muss angegeben werden
AS
RETURN
(
  SELECT 1 'Granted' WHERE 
    USER_NAME() = @RoleOrUsername OR IS_MEMBER(ISNULL(@RoleOrUsername, 'PUBLIC')) = 1
);

Um eine Security Policy anzulegen, die auch gleich ein Filter Predicate enthält, kann der Code in Listing 3 verwendet werden. Dabei wird der Inhalt einer Spalte (SecurityDescriptor) der Funktion als Parameter übergeben werden, damit diese abhängig vom Inhalt jede Zeile filtern kann.

CREATE SECURITY POLICY SecretFilter
ADD FILTER PREDICATE dbo.fn_AccessFilter([SecurityDescriptor]) ON [dbo].[Employees],
ADD BLOCK PREDICATE dbo.fn_AccessFilter([SecurityDescriptor]) ON [dbo].[Employees] AFTER INSERT,
ADD BLOCK PREDICATE dbo.fn_AccessFilter([SecurityDescriptor]) ON [dbo].[Employees] BEFORE DELETE,
ADD BLOCK PREDICATE dbo.fn_AccessFilter([SecurityDescriptor]) ON [dbo].[Employees] BEFORE UPDATE;
-- Weitere Filter/Blockprädikate für weitere Tabelle

Ein einzelnes Prädikat kann nicht deaktiviert werden, aber dafür die gesamte Security Policy mit allen darin definierten Prädikaten (Listing 4).

-- Securtiy Policy aktivieren
ALTER SECURITY POLICY [dbo].[SecretFilter] WITH (STATE = OFF);
-- Securtiy Policy deaktivieren
ALTER SECURITY POLICY [dbo].[SecretFilter] WITH (STATE = ON);

Das Deaktivieren ist praktisch für Wartungsarbeiten oder Datenim-/-exporte und ist notwendig, da es zu keinem Zeitpunkt zwei Security Policies geben darf, die für die gleiche Tabelle definiert sind. Es ist also sichergestellt, dass es nur eine Funktion gibt, die aktiv filtert – widersprüchliche Filterergebnisse sind also nicht möglich.

kansy_SQL_2016_Sicherheitskonzept_1

Abb. 1: Row-Level Security im SQL Server Management Studio

Row-Level Security lässt sich auch im SQL Server Management Studio im Object Explorer unter „Security“ und in den Eigenschaften einer Tabelle einsehen (Abb. 1).

Dynamic Data Masking

Werden Zeilen zurückgeliefert, so besteht mit Dynamic Data Masking die Option, den Inhalt sensibler Spalten nur maskiert, also in anonymisierter Form zu liefern. Aggregate, Filter und Sortierungen werden dabei jedoch immer vor der Maskierung ausgeführt und funktionieren damit wie erwartet. Der unmaskierte Inhalt einzelner Spalten steht nur zur Verfügung, wenn der Benutzer über das notwendige Recht verfügt.

Für die Maskierung stehen unterschiedliche Funktionen zur Verfügung, die jedoch nur genutzt, jedoch nicht nach eigenen Wünschen angepasst werden können. Welche Maskierungsfunktion für welche Spalte verwendet werden soll, wird direkt in der Tabellendefinition angegeben. Das Spielfeld aus Listing 1 muss daher nochmal überdacht und angepasst werden. Da die Tabelle schon besteht, kommt hier ALTER TABLE…ALTER COLUMN…ADD zum Einsatz. Bei neuen Tabellen kann die Maskierungsfunktion direkt in der CREATE TABLE-Anweisung untergebracht werden (Listing 5).

ALTER TABLE [dbo].[Employees]
ALTER COLUMN [Name] ADD MASKED WITH (FUNCTION = 'partial(1,"-",2)');
ALTER TABLE [dbo].[Employees]
ALTER COLUMN [Address] ADD MASKED WITH (FUNCTION = 'default()');
ALTER TABLE [dbo].[Employees]
ALTER COLUMN [Email] ADD MASKED WITH (FUNCTION = 'email()');
ALTER TABLE [dbo].[Employees]
ALTER COLUMN [Salary] ADD MASKED WITH (FUNCTION = 'random(1, 1999)');

Befinden sich Daten in der Tabelle und werden diese abgefragt, kommt es darauf an, ob der Anwender über das neue UNMASK-Recht verfügt. Ist dies der Fall, werden die Daten nicht maskiert und können so verwendet werden, wie sie in die Tabelle eingefügt wurden (Abb. 2).

kansy_SQL_2016_Sicherheitskonzept_2

Abb. 2: Dynamisch maskierte Daten

Das notwendige Recht wird wie folgt vergeben: GRANT UNMASK TO UserMitUNMASK. Und damit offenbart sich aktuell ein großes Manko von Dynamic Data Masking – das UNMASK-Recht wird in der Datenbank für alle Tabellen vergeben. Feingranularer nur auf einzelne Tabellen oder Schemata geht es leider nicht.

Mandantenfilter

Das Filtern nach Mandanten oder ähnliche Einschränkungen sind optimal für die Sicherheit von Daten, die vor ihrer Weiterverarbeitung gefiltert werden müssen.

Denkbar wäre hier zwar auch ein Lösungsansatz mit gespeicherten Prozeduren oder Tabellenwertfunktionen, allerdings mit dem Nachteil, dass für jeden Aufruf ein Parameter benötigt wird und im Falle der gespeicherten Prozeduren weitere Verknüpfungen (Joins) nicht möglich sind. Auch aus Performancegründen wären Sichten die bessere Wahl, allerdings erlauben diese keine Parameter.

Es wäre also eine Idee, den Mandantenschlüssel über den Kontext einer Verbindung zur Verfügung zu stellen, damit z. B. Sichten diesen nutzen können. Zwar ist dies schon ohne Eigenbau ab SQL Server 2008 mit CONTEXT_INFO machbar, hier steht allerdings nur ein einzelner Wert zur Verfügung, der auch noch jedes Mal in den richtigen Datentyp gecastet werden muss. SQL Server 2016 macht dies mit SESSION_CONTEXT besser, in dem hier Schlüssel-Werte-Paare erlaubt sind. Natürlich sind diese Werte dann für jede Session (also Verbindung zum SQL Server) individuell.

Nun muss noch die Tabelle aus Listing 1 erweitert werden, um jeder Zeile einem Mandanten zuzuordnen und für den Zugriff auf die Tabelle muss eine Sicht angelegt werden, die die notwendige Filterung durchführt (Listing 6).

ALTER TABLE [dbo].[Employees] ADD Mandator VARCHAR(10) NULL;
GO

CREATE VIEW [dbo].[vwEmployees] AS
SELECT * FROM [dbo].[Employees] 
WHERE Mandator IS NULL OR 
      Mandator = SESSION_CONTEXT(N'MandatorKey');

Der Mandantenschlüssel kann dann mit einem solchen Aufruf festgelegt werden:

EXEC sp_set_session_context @key = 'MandatorKey',  @value = 'MK01';

Die Sicht filtert alle Zeilen, die zum Mandanten gehören oder die keinem speziellen Mandaten zugeordnet sind. Je nach Datenbankdesign und Datenmenge bietet sich ein numerischer Mandantenschlüssel (Primary Key einer Mandantentabelle) an.

Das Zusammenspiel

Das Zusammenspiel sieht nun denkbar einfach aus. In jeder Verbindung muss am Anfang der gewünschte Mandant, genauer dessen Schlüssel, besetzt werden (Listing 7).

EXEC sp_set_session_context @key = 'MandatorKey', @value = 'MK01';
GO
SELECT * FROM dbo.vwEmployees;

Über die benutzerdefinierte Funktion, die in der Row-Level Security verwendet wird, wird darüber hinaus jede Zeile ausgefiltert, die nicht für den Benutzer verfügbar sein soll. Wird eine Zeile geliefert, so entscheidet das UNMASK-Recht, ob besonders sensible Daten demaskiert werden sollen oder nicht.

Zusammenfassung

SQL Server 2016 bietet einige neue Funktionen, die es einfacher machen, für die benötigte Datensicherheit zur sorgen.

Das Dreigespann Row-Level Security, Dynamic Data Masking und Session Context ermöglicht die Einschränkung auf nur berechtigte Zeilen einer Tabelle und stellt sicher, das besonders sensible Daten bei fehlender Berechtigung nur anonymisiert geliefert werden.

Der Umsetzung einer maßgeschneiderten Sicherheit steht also nichts mehr im Wege!

Das Video zum Text

Als Ergänzung zum Artikel gibt es ein Video, das die vorgestellten Skripte und ihre Ergebnisse zeigt: http://bit.ly/1Z9WpLm

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -