Implementowanie zabezpieczeń na poziomie wiersza
Zabezpieczenia na poziomie wiersza nie korzystają z szyfrowania i działają na poziomie bazy danych, aby ograniczyć dostęp do tabeli przy użyciu zasad zabezpieczeń opartych na członkostwie w grupie lub kontekście autoryzacji. To funkcjonalnie jest równoważne klauzuli WHERE .
Zasady zabezpieczeń wywołują funkcję w tabeli wbudowanej, aby chronić dostęp do wierszy w tabeli.
W zależności od atrybutu użytkownika predykat określa, czy ten użytkownik ma dostęp do odpowiednich informacji. Po uruchomieniu zapytania względem tabeli zasady zabezpieczeń stosują funkcję predykatu. W zależności od wymagań biznesowych zabezpieczenia na poziomie wiersza mogą być tak proste, jak WHERE CustomerId = 29 i tak złożone, jak to wymagane.
Istnieją dwa typy zasad zabezpieczeń obsługiwanych przez zabezpieczenia na poziomie wiersza:
Predykaty filtru — ograniczanie dostępu do danych naruszających predykat.
Access Definicja SELECT Nie można wyświetlić wierszy, które są filtrowane. UPDATE Nie można zaktualizować wierszy, które są filtrowane. DELETE Nie można usunąć wierszy, które są filtrowane. INSERT Nie dotyczy. Blokuj predykaty — ograniczaj zmiany danych naruszające predykat.
Access Definicja PO WSTAWIENIU Uniemożliwia użytkownikom wstawianie wierszy z wartościami naruszającymi predykat. PO AKTUALIZACJI Uniemożliwia użytkownikom aktualizowanie wierszy do wartości naruszających predykat. PRZED AKTUALIZACJĄ Uniemożliwia użytkownikom aktualizowanie wierszy, które obecnie naruszają predykat. PRZED USUNIĘCIEM Blokuje operacje usuwania, jeśli wiersz narusza predykat.
Ponieważ kontrola dostępu jest skonfigurowana i stosowana na poziomie bazy danych, zmiany aplikacji są minimalne — jeśli istnieją. Ponadto użytkownicy mogą mieć bezpośredni dostęp do tabel i mogą wykonywać zapytania dotyczące własnych danych.
Zabezpieczenia na poziomie wiersza są implementowane w trzech głównych krokach:
- Utwórz użytkowników lub grupy, do których chcesz odizolować dostęp.
- Utwórz wbudowaną funkcję wartości tabeli, która filtruje wyniki na podstawie zdefiniowanego predykatu.
- Utwórz zasady zabezpieczeń dla tabeli, przypisując wcześniej utworzoną funkcję.
Następujące polecenia języka T-SQL pokazują, jak używać zabezpieczeń na poziomie wiersza w scenariuszu, w którym dostęp użytkowników jest segregowany przez dzierżawę:
-- Create supporting objects for this example
CREATE TABLE [Sales] (SalesID INT,
ProductID INT,
TenantName NVARCHAR(10),
OrderQtd INT,
UnitPrice MONEY)
GO
INSERT INTO [Sales] VALUES (1, 3, 'Tenant1', 5, 10.00);
INSERT INTO [Sales] VALUES (2, 4, 'Tenant1', 2, 57.00);
INSERT INTO [Sales] VALUES (3, 7, 'Tenant1', 4, 23.00);
INSERT INTO [Sales] VALUES (4, 2, 'Tenant2', 2, 91.00);
INSERT INTO [Sales] VALUES (5, 9, 'Tenant3', 5, 80.00);
INSERT INTO [Sales] VALUES (6, 1, 'Tenant3', 5, 35.00);
INSERT INTO [Sales] VALUES (7, 3, 'Tenant4', 8, 11.00);
-- View all the rows in the table
SELECT * FROM Sales;
Następnie utwórz użytkowników i przyznaj im dostęp do tabeli Sales . W tym przykładzie każdy użytkownik jest odpowiedzialny za określoną dzierżawę. Użytkownik TenantAdmin ma dostęp do wyświetlenia danych ze wszystkich dzierżaw.
CREATE USER [TenantAdmin] WITH PASSWORD = '<strong password>'
GO
CREATE USER [Tenant1] WITH PASSWORD = '<strong password>'
GO
CREATE USER [Tenant2] WITH PASSWORD = '<strong password>'
GO
CREATE USER [Tenant3] WITH PASSWORD = '<strong password>'
GO
CREATE USER [Tenant4] WITH PASSWORD = '<strong password>'
GO
GRANT SELECT ON [Sales] TO [TenantAdmin]
GO
GRANT SELECT ON [Sales] TO [Tenant1]
GO
GRANT SELECT ON [Sales] TO [Tenant2]
GO
GRANT SELECT ON [Sales] TO [Tenant3]
GO
GRANT SELECT ON [Sales] TO [Tenant4]
GO
Następnie utworzymy nowy schemat, funkcję w tekście z wartością tabeli i przyznamy użytkownikowi dostęp do nowej funkcji. Predykat WHERE @TenantName = USER_NAME() OR USER_NAME() = 'TenantAdmin' ocenia, czy nazwa użytkownika wykonująca zapytanie jest zgodna z wartościami kolumn TenantName .
CREATE SCHEMA sec;
GO
--Create the filter predicate
CREATE FUNCTION sec.tvf_SecurityPredicatebyTenant(@TenantName AS NVARCHAR(10))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS result
WHERE @TenantName = USER_NAME() OR USER_NAME() = 'TenantAdmin';
GO
--Grant users access to inline table-valued function
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [TenantAdmin]
GO
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [Tenant1]
GO
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [Tenant2]
GO
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [Tenant3]
GO
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [Tenant4]
GO
--Create security policy and add the filter predicate
CREATE SECURITY POLICY sec.SalesPolicy
ADD FILTER PREDICATE sec.tvf_SecurityPredicatebyTenant(TenantName) ON [dbo].[Sales]
WITH (STATE = ON);
GO
Na tym etapie możemy przetestować dostęp:
EXECUTE AS USER = 'TenantAdmin';
SELECT * FROM dbo.Sales;
REVERT;
EXECUTE AS USER = 'Tenant1';
SELECT * FROM dbo.Sales;
REVERT;
EXECUTE AS USER = 'Tenant2';
SELECT * FROM dbo.Sales;
REVERT;
EXECUTE AS USER = 'Tenant3';
SELECT * FROM dbo.Sales;
REVERT;
EXECUTE AS USER = 'Tenant4';
SELECT * FROM dbo.Sales;
REVERT;
Użytkownik TenantAdmin powinien zobaczyć wszystkie wiersze. Użytkownicy dzierżawy Tenant1, Tenant2, Tenant3 i Tenant4 powinni widzieć tylko własne wiersze.
Jeśli zmienisz zasady zabezpieczeń za WITH (STATE = OFF);pomocą polecenia , zauważysz, że użytkownicy zobaczą wszystkie wiersze.
Uwaga
Istnieje ryzyko wycieku informacji, jeśli osoba atakująca napisze zapytanie ze specjalnie spreparowaną WHERE klauzulą, a na przykład błąd dzielenia przez zero, aby wymusić wyjątek, jeśli WHERE warunek jest spełniony. Jest to nazywane atakiem kanału bocznego. Warto ograniczyć możliwość uruchamiania nieplanowanych zapytań przez użytkowników podczas korzystania z zabezpieczeń na poziomie wiersza.
Przypadek użycia
Zabezpieczenia na poziomie wiersza są idealne dla wielu scenariuszy, w tym:
- Gdy musisz odizolować dostęp do działu na poziomie wiersza.
- Jeśli musisz ograniczyć dostęp do danych klientów tylko do danych istotnych dla ich firmy.
- Jeśli musisz ograniczyć dostęp do celów zgodności.
Najlepsze rozwiązania
Poniżej przedstawiono kilka najlepszych rozwiązań, które należy wziąć pod uwagę podczas implementowania zabezpieczeń na poziomie wiersza:
- Utwórz oddzielny schemat dla funkcji predykatu i zasad zabezpieczeń.
- Unikaj konwersji typów w funkcjach predykatu.
- Unikaj używania nadmiernych sprzężeń tabeli i rekursji w funkcjach predykatu.