Implementar a segurança em nível de linha

Concluído

A RLS (segurança em nível de linha) não usa criptografia e opera no nível do banco de dados para restringir o acesso a uma tabela usando uma política de segurança baseada no contexto de associação de grupo ou autorização. Isso funcionalmente é equivalente a uma cláusula WHERE.

A política de segurança invoca uma função com valor de tabela embutida para proteger o acesso às linhas em uma tabela.

Dependendo do atributo de um usuário, o predicado determina se esse usuário tem acesso às informações relevantes. Quando você executa uma consulta em uma tabela, a política de segurança aplica a função de predicado. Dependendo dos requisitos de negócios, a RLS pode ser tão simples quanto WHERE CustomerId = 29 ou tão complexa quanto necessário.

Há dois tipos de políticas de segurança compatíveis com a segurança em nível de linha:

  • Predicados de filtro – restringir o acesso a dados que viola o predicado.

    Acesso Definição
    SELECT Não é possível exibir linhas filtradas.
    UPDATE Não é possível atualizar linhas filtradas.
    DELETE Não é possível excluir linhas filtradas.
    INSERT Não aplicável.
  • Bloquear predicados – restringir alterações de dados que violam o predicado.

    Acesso Definição
    DEPOIS DE INSERIR Impede que os usuários insiram linhas com valores que violam o predicado.
    APÓS A ATUALIZAÇÃO Impede que os usuários atualizem linhas para valores que violam o predicado.
    ANTES DA ATUALIZAÇÃO Impede que os usuários atualizem linhas que atualmente violam o predicado.
    ANTES DE APAGAR Bloqueia operações de exclusão se a linha violar o predicado.

Como o controle de acesso é configurado e aplicado no nível do banco de dados, as alterações de aplicativo são mínimas – se houver. Além disso, os usuários podem ter acesso direto às tabelas e consultar seus próprios dados.

A segurança em nível de linha é implementada em três etapas principais:

  1. Crie os usuários ou grupos que você deseja isolar o acesso.
  2. Crie a função com valor de tabela embutida que filtra os resultados com base no predicado definido.
  3. Crie uma política de segurança para a tabela, atribuindo a função criada anteriormente.

Os seguintes comandos T-SQL demonstram como usar o RLS em um cenário em que o acesso do usuário é segregado pelo locatário:

-- 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;

Crie os usuários e conceda acesso à tabela Vendas. Neste exemplo, cada usuário é responsável por um locatário específico. O usuário TenantAdmin tem acesso para ver dados de todos os locatários.

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

Em seguida, criamos um esquema, uma função embutida com valor de tabela e concedemos acesso de usuário à nova função. O predicado WHERE @TenantName = USER_NAME() OR USER_NAME() = 'TenantAdmin' avalia se o nome de usuário que executa a consulta corresponde aos valores da coluna 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

Neste ponto, estamos prontos para testar o acesso:

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;

O usuário TenantAdmin deve ver todas as linhas. Os usuários Tenant1, Tenant2, Tenant3 e Tenant4 só devem ver suas próprias linhas.

Se você alterar a política de segurança com WITH (STATE = OFF);, perceberá que os usuários veem todas as linhas.

Captura de tela dos comandos T-SQL para alterar uma política de segurança.

Observação

Há um risco de vazamento de informações se um invasor gravar uma consulta com uma cláusula especialmente criada WHERE e, por exemplo, um erro dividir por zero, para forçar uma exceção se a condição WHERE for verdadeira. Isso é conhecido como um ataque de canal lateral. É sábio limitar a capacidade dos usuários de executar consultas não planejadas ao usar a segurança em nível de linha.

Caso de uso

A segurança em nível de linha é ideal para muitos cenários, incluindo:

  • Quando você precisar isolar o acesso departamental no nível da linha.
  • Quando você precisar restringir o acesso a dados dos clientes apenas aos dados relevantes para sua empresa.
  • Quando você precisar restringir o acesso para fins de conformidade.

Práticas recomendadas

Aqui estão algumas melhores práticas a serem consideradas ao implementar a RLS:

  • Crie um esquema separado para funções de predicado e políticas de segurança.
  • Evite conversões de tipo em funções de predicado.
  • Evite usar junções de tabela excessivas e recursão em funções de predicado.