Clique no banner para conhecer e adquirir o meu treinamento de Bancos de Dados no Azure

SQL Server 2016 – Como proteger seus dados utilizando o Row Level Security (RLS)

Post Views 1,610 views
Reading time 10 minutes

Fala pessoal!!
Nesse artigo, eu gostaria de apresentar para vocês um recurso de segurança muito interessante que o Microsoft SQL Server ganhou a partir da versão 2016, que é o Row Level Security (RLS) ou Segurança em nível de linha.

Como funciona o Row Level Security (RLS)

Visualizar conteúdo
O Row Level Security permite controlar o acesso às linhas em uma tabela de acordo com o usuário ou grupo a qual faz parte, como se tivesse sido aplicado um filtro no WHERE da consulta para limitar quais registros ele pode visualizar. Para o usuário que está realizando as consultas no banco, o processo de filtragem dos dados é totalmente transparente, de forma que ele não consiga perceber que, embora ele esteja executando um SELECT * FROM tabela, a consulta não está retornando todos os dados da tabela.

Isso simplifica o design e a segurança dos sistemas e aplicações, garantindo que, por exemplo, os funcionários possam acessar somente as linhas de dados que são relevantes para seu departamento ou restringir o acesso do cliente a somente aos dados relevantes para a empresa desse cliente. Tudo isso, feito à nível de banco de dados, sem precisar realizar qualquer alteração na aplicação.

Vale ressaltar que existem 2 tipos de uso do RLS:

  • Filtro de linhas (FILTER PREDICATE): Permite aplicar um filtro de linhas nas consultas realizadas com SELECT, retornando apenas os registros que o usuário pode visualizar
  • Bloqueio de operações (BLOCK PREDICATE): Permite bloquear determinadas operações a nível de linha, no qual o usuário em questão não poderia realizar (Ex: INSERT numa tabela utilizando um Id_Usuario diferente do dele)


Como implementar o Row Level Security (RLS)

Visualizar conteúdo
Para implementar o RLS nas suas tabelas, você irá criar uma função para fazer a filtragem dos registros e utilizar o comando CREATE SECURITY POLICY para forçar o uso dessa função nas consultas realizadas pela tabela.

Criação dos dados de teste para o exemplo

Result:

Criação dos usuários utilizados nos testes e permissões
Agora vou criar alguns usuários no banco de dados para testar o RLS e dar permissão para esses usuários poderem acessar as tabelas Pedidos e Usuarios.

Criação do schema “rls”
Como boa prática, vou criar também um novo schema (rls) para agregar as funções que serão criadas para uso com o RLS. Como será necessário criar 1 função para cada critério ou filtro que você queira aplicar o RLS e 1 policy para cada tabela, é mais organizado que todas essas funções fiquem em um esquema dedicado.

Criação da função fncId_Usuario
Utilizando a função fncId_Usuario, posso aplicar um filtro em todas as tabelas (se eu criar policies para isso) que tenham um campo com o identificador do usuário. Nessa função, é informado o Id_Usuario que está na tabela em questão e a função irá buscar na tabela de usuários qual o identificador (Id_Usuario) do usuário logado que está realizando o SELECT na tabela.

Vale lembrar que o filtro é aplicado mesmo para usuários sysadmin. Ou seja, mesmo o usuário sysadmin só poderá visualizar os registros onde o Id_Usuario seja igual ao Id desse usuário (ou ele não poderá visualizar nenhum registro).

Criação do policy rls.Pedidos
Com o comando abaixo, vamos aplicar a policy rls.Pedidos utilizando a função fncId_Usuario e assim, ativar o Row Level Security (RLS) na tabela Pedidos.

Testes do Row Level Security:
Agora, vamos aos testes práticos e ver como a Segurança em nível de linha funciona realmente.

Result:

Outro exemplo para o Row Level Security (RLS)

Visualizar conteúdo
Acabei demonstrando um uso bem comum do RLS, mas pouco documentado em artigos, que é aplicando o RLS utilizando um ID proveniente de outra tabela. Utilizando a mesma base anterior, vou demonstrar rapidamente um uso mais simples, que é utilizando o próprio login do usuário sem utilizar um SELECT para recuperar alguma informação.

Criação da função e da policy

Testes reais do RLS

Result:

Utilizando o BLOCK PREDICATE do Row Level Security (RLS)

Visualizar conteúdo
Após demonstrar o uso do FILTER PREDICATE, que permite filtrar registros de uma consulta (SELECT), agora vou demonstrar o uso do BLOCK PREDICATE, que impede uma determinada operação de DML (INSERT, UPDATE e DELETE)

Vale lembrar que podemos utilizar os predicados FILTER e BLOCK ao mesmo tempo, na mesma policy.

Código-fonte da policy

Agora, vamos impersonar o usuário “dirceu.resende” e tentar inserir um usuário com outro login na tabela que não seja o “dirceu.resende”:

Result:

Msg 33504, Level 16, State 1, Line 4
The attempted operation failed because the target object ‘dirceuresende.dbo.Usuarios’ has a block predicate that conflicts with this operation. If the operation is performed on a view, the block predicate might be enforced on the underlying table. Modify the operation to target only the rows that are allowed by the block predicate.
The statement has been terminated.

Como vocês puderam observar, não foi possível inserir o registro, pois o BLOCK PREDICATE evitou a inserção uma vez que o Cd_Usuario que estava sendo inserido não era o que o usuário “dirceu.resende” tinha permissão para inserir.

O mesmo acontece quando tentamos realizar um UPDATE:

Result:

Msg 33504, Level 16, State 1, Line 17
The attempted operation failed because the target object ‘dirceuresende.dbo.Usuarios’ has a block predicate that conflicts with this operation. If the operation is performed on a view, the block predicate might be enforced on the underlying table. Modify the operation to target only the rows that are allowed by the block predicate.
The statement has been terminated.

Entretanto, se tentarmos atualizar os registros do próprio usuário logado, vemos que a operação é realizada com sucesso:

Result:

Como criamos mais 2 condições na função fncUsuario para permitir que usuários sysadmin e/ou db_owner possam acessar os dados, esses 2 perfis de usuários também poderão inserir/atualizar os dados normalmente:

Boas práticas para o Row Level Security

Visualizar conteúdo
  • Evite conversões de tipo em funções de predicado para evitar possíveis erros de tempo de execução.
  • Evite utilizar outras funções dentro da função de filtragem, para evitar a degradação do desempenho. O otimizador de consulta tentará detectar recursões diretas, mas não há garantia de que encontrará recursões indiretas (isto é, onde uma segunda função chama a função de predicado).
  • Evite usar joins de tabelas em excesso em funções de predicado, para maximizar o desempenho.
  • Evite criar funções de filtragens que dependam de determinadas configurações de sessão aplicadas utilizando o comando SET (Comparações/operações com NULL, funções de primeiro dia da semana e funções que dependam da linguagem atual ou DATEFORMAT).

Row Level Security e seu impacto na performance

Visualizar conteúdo
Como falei logo acima, ao utilizar o recurso de Segurança a nível de linha (RLS), estamos aplicando um filtro na nossa query através de uma policy e uma função de filtro. Isso já faz com que o plano de execução da consulta seja alterado e, dependendo da lógica da sua função, pode até provocar problemas de performance de acordo com a quantidade de utilizações.

Plano de execução SEM o RLS ativado

Plano de execução COM o RLS ativado

Como desativar o Row Level Security (RLS)

Visualizar conteúdo
Para desativar o RLS, basta utilizar a sintaxe abaixo:

E para excluir os objetos do RLS, você pode utilizar os comandos abaixo:

Row Level Security (RLS) e compatibilidade entre recursos

Visualizar conteúdo
De modo geral, a segurança no nível de linha funcionará conforme o esperado entre os recursos. No entanto, há algumas exceções. Esta seção documenta várias observações e limitações para o uso da segurança em nível de linha com determinados recursos do SQL Server.

  • DBCC SHOW_STATISTICS: Os relatórios de estatísticas podem exibir informações que estariam protegidas por uma política de segurança. Por esse motivo, para exibir um as estatísticas de uma tabela protegidas com o RLS, o usuário deve ter ser proprietário da tabela ou deve ser um membro da role sysadmin, db_owner ou db_ddladmin.
  • Filestream: RLS não é compatível com Filestream.
  • Polybase: RLS não é compatível com o Polybase.
  • Memory-Optimized Tables: A função usada como um predicado de segurança em uma tabela com otimização de memória deve ser definida usando a opção WITH NATIVE_COMPILATION. Com essa opção, os recursos de linguagem não permitidos pelas tabelas com otimização de memória serão banidos e o erro apropriado será emitido no momento da criação.
  • Views indexadas: De modo geral, as políticas de segurança podem ser criadas sobre as views, e as views podem ser criadas sobre as tabelas que são associadas pelo RLS. No entanto, as views indexadas não podem ser criadas sobre as tabelas que têm uma política de segurança RLS, pois as pesquisas de linha pelo índice contornariam a política.
  • Change Data Capture (CDC): O CDC pode permitir o vazamento das informações de linhas inteiras, uma vez que, mesmo utilizando o RLS, todas as alterações realizadas serão armazenadas na tabela de histórico, e qualquer usuário com permissão nessa tabela poderá visualizar essas informações
  • Change Tracking: O Controle de Alterações (Change Tracking) pode deixar vazar a chave primária de linhas que deve ser filtrada para usuários com as permissões SELECT e VIEW CHANGE TRACKING. Os valores de dados reais não vazam; apenas o fato de que a coluna A foi atualizada/inserida/excluída para a linha com a chave primária B. Isso será um problema se a chave primária contiver um elemento confidencial, como um Número de Seguro Social. No entanto, na prática, esse CHANGETABLE é quase sempre unido à tabela original para obtenção de dados mais recentes.
  • Full-Text Search: Uma queda no desempenho é esperada em consultas que usam as funções de Full-Text Search e Pesquisa Semântica devido a uma junção extra apresentada para aplicar a segurança em nível de linha e evitar a perda das chaves primárias de linhas que devem ser filtradas: CONTAINSTABLE, FREETEXTTABLE, semantickeyphrasetable, semanticsimilaritydetailstable, semanticsimilaritytable.
  • Índices Columnstore: A RLS é compatível com índices columnstore clusterizados e não clusterizados. No entanto, como a segurança no nível de linha se aplica a uma função, é possível que o otimizador possa modificar o plano de consulta, de modo que ele não use o modo de lote (batch mode).
  • Views Particionadas: Os predicados de bloqueio não podem ser definidos em views particionadas, e as views particionadas não podem ser criadas sobre as tabelas que usam predicados de bloqueio. Os predicados de filtro são compatíveis com views particionadas.
  • Temporal tables: As tabelas temporais são compatíveis com a RLS. No entanto, os predicados de segurança na tabela atual não são replicados automaticamente na tabela de histórico. Para aplicar uma política de segurança às tabelas atual e de histórico, você deverá adicionar individualmente um predicado de segurança em cada tabela.

Referências: https://docs.microsoft.com/en-us/sql/relational-databases/security/row-level-security

And that's it, folks!
Um abraço e até a próxima.