Implementera säkerhet på radnivå
Säkerhet på radnivå (RLS) använder inte kryptering och fungerar på databasnivå för att begränsa åtkomsten till en tabell med hjälp av en säkerhetsprincip baserat på gruppmedlemskap eller auktoriseringskontext. Detta är funktionellt likvärdigt med en WHERE sats.
Säkerhetsprincipen anropar en infogad tabellvärdesfunktion för att skydda åtkomsten till raderna i en tabell.
Beroende på attributet för en användare avgör predikatet om användaren har åtkomst till relevant information. När du kör en fråga mot en tabell tillämpar säkerhetsprincipen predikatfunktionen. Beroende på affärskraven kan RLS vara så enkelt som WHERE CustomerId = 29 eller så komplext som det behövs.
Det finns två typer av säkerhetsprinciper som stöds av säkerhet på radnivå:
Filterpredikat – begränsa dataåtkomst som bryter mot predikatet.
Access Definition SELECT Det går inte att visa rader som filtreras. UPDATE Det går inte att uppdatera rader som filtreras. DELETE Det går inte att ta bort rader som filtreras. INSERT Ej tillämpbart. Blockera predikat – begränsa dataändringar som bryter mot predikatet.
Access Definition EFTER INSERT Hindrar användare från att infoga rader med värden som bryter mot predikatet. EFTER UPPDATERING Hindrar användare från att uppdatera rader till värden som bryter mot predikatet. FÖRE UPPDATERING Hindrar användare från att uppdatera rader som för närvarande bryter mot predikatet. FÖRE BORTTAGNING Blockerar borttagningsåtgärder om raden bryter mot predikatet.
Eftersom åtkomstkontroll har konfigurerats och tillämpats på databasnivå är programändringarna minimala – om några. Användarna kan också ha direkt åtkomst till tabellerna och köra frågor mot sina egna data.
Säkerhet på radnivå implementeras i tre huvudsteg:
- Skapa de användare eller grupper som du vill isolera åtkomsten till.
- Skapa den infogade tabellvärdesfunktionen som filtrerar resultatet baserat på predikatet som definierats.
- Skapa en säkerhetsprincip för tabellen och tilldela funktionen som skapades tidigare.
Följande T-SQL-kommandon visar hur du använder RLS i ett scenario där användaråtkomst separeras av klientorganisationen:
-- 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;
Skapa sedan användarna och ge dem åtkomst till tabellen Försäljning . I det här exemplet ansvarar varje användare för en specifik klientorganisation. TenantAdmin-användaren har åtkomst till att se data från alla klienter.
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
Sedan skapar vi ett nytt schema, en infogad tabellvärdesfunktion, och ger användaren åtkomst till den nya funktionen. Predikatet WHERE @TenantName = USER_NAME() OR USER_NAME() = 'TenantAdmin' utvärderar om användarnamnet som kör frågan matchar kolumnvärdena för 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
Nu är vi redo att testa åtkomsten:
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;
TenantAdmin-användaren bör se alla rader. Användare av klientorganisation1, klientorganisation 2, klientorganisation 3 och klient4 bör bara se sina egna rader.
Om du ändrar säkerhetsprincipen med WITH (STATE = OFF);ser du att användarna ser alla rader.
Kommentar
Det finns en risk för informationsläckage om en angripare skriver en fråga med en särskilt utformad sats WHERE och till exempel ett divide-by-zero-fel för att framtvinga ett undantag om villkoret WHERE är sant. Detta kallas för en sidokanalattack. Det är klokt att begränsa användarnas möjlighet att köra oplanerade frågor när de använder säkerhet på radnivå.
Användningsfall
Säkerhet på radnivå är perfekt för många scenarier, bland annat:
- När du behöver isolera avdelningsåtkomst på radnivå.
- När du behöver begränsa kundernas dataåtkomst till endast de data som är relevanta för deras företag.
- När du behöver begränsa åtkomsten i efterlevnadssyfte.
Metodtips
Här följer några metodtips när du implementerar RLS:
- Skapa ett separat schema för predikatfunktioner och säkerhetsprinciper.
- Undvik typkonverteringar i predikatfunktioner.
- Undvik att använda för stora tabellkopplingar och rekursion i predikatfunktioner.