Implementando politica de segurança (row level) usando o VIRTUAL PRIVATE DATABASE (VPD)

O Oracle Virtual Private Database é uma feature inclusa na versão Enterprise Edition, sendo introduzida a partir do Oracle 8i.

O principal objetivo do VPD é permitir a criação de políticas para acesso aos dados, em nível de linha e coluna (disponibilizada a partir da versão 10g). Basicamente o VPD inclui dinamicamente uma clausula where nas consultas que são realizadas nas tabelas/views/sinônimos que possuem o VPD ativo.

As políticas criadas com o VPD podem ser simples ou complexas, dependendo dos requerimentos de segurança necessários. Estas políticas, por serem associadas diretamente aos objetos no banco de dados, são aplicadas automaticamente a qualquer usuário e meio de acesso aos dados. Desde que estes usuários não tenham a permissão EXEMPT ACCESS POLICY.

Quando um usuário, seja direta ou indiretamente acessa um objeto com VPD ativo, o Oracle modifica a consulta SQL executada. Esta modificação cria uma clausula where retornada por uma função usada na implementação da politica de segurança. A modificação ocorre de forma transparente para o usuário e desta forma não necessita de nenhum ajuste a "nível de aplicação". O VPD pode ser aplicado a operações de select, insert, update, index e delete. 

Neste artigo irei abordar um exemplo de criação de VPD "simples" a nível de linha.

Primeiramente estarei realizando a criação de uma tabela chamada notas, na qual estaremos aplicando uma política VPD para que apenas os usuários autorizados possam visualizar as informações contidas.
SQL> show user;
USER is "LAMIM"
SQL> create table notas(
id_aluno number(4),
id_curso number (2),
nome_aluno varchar2 (100),
 nota number(4,1)
);  2    3    4    5    6

Table created.
SQL>
Criada a tabela, vamos inserir alguns valores na mesma afim de possibilitar nossa demonstração de um política VPD.
SQL> 
SQL> insert into notas values (1,15,'Scot',8.0);

1 row created.

SQL> insert into notas values (2,15,'Smith',6.5);

1 row created.

SQL> insert into notas values (3,25,'Tiger',3.3);

1 row created.

SQL> insert into notas values (4,25,'Sarah',4.7);

1 row created.

SQL> insert into notas values (5,25,'Jessie',10);

1 row created.

SQL> insert into notas values (7,30,'Roth',9.3);

1 row created.

SQL> insert into notas values (8,30,'John',10);

1 row created.

SQL> insert into notas values (9,10,'Allen',7.7);

1 row created.

SQL> insert into notas values (10,10,'Paul',5.1);

1 row created.

SQL> commit;

Commit complete.
Para que seja possível demonstrar o funcionamento do VPD, será criado o usuário consulta, que terá permissão de leitura na tabela notas.
SQL> create user consulta identified by consulta;

User created.

SQL>
SQL> grant  create session to consulta;

Grant succeeded.

SQL> grant select on notas to consulta;

Grant succeeded.

SQL>
SQL> select * from lamim.notas;

  ID_ALUNO  ID_CURSO NOME_ALUNO       NOTA
---------- ---------- ---------- ----------
         1         15 Scot                8
         2         15 Smith             6,5
         3         25 Tiger             3,3
         4         25 Sarah             4,7
         5         25 Jessie             10
         7         30 Roth              9,3
         8         30 John               10
         9         10 Allen             7,7
        10         10 Paul              5,1

9 rows selected.
SQL> 
Apos a criação do usuário, realizando um select na tabela notas do schema lamim, podemos ver todos os dados da tabela. Porém imagine que exista uma regra de negócio na qual o usuário consulta só pode visualizar os alunos vinculados ao curso de id 15 e 25.
Vamos criar um VPD para atender esta regra. É importante destacar que a complexidade da politica, depende exclusivamente da necessidade de segurança a ser implementada.

Para um controle mais "dinâmico" do meu VPD, estarei criando duas tabelas de controle chamadas DENY_ACESS onde serão cadastrados os usuário que serão afetados (limitados - usuário consulta) pela política e a  BLOCKED_ACCESS onde serão cadastrados os registros que serão afetados (limitados id_curso 10 e 30).
SQL>
SQL> create table DENY_ACCESS(
nome varchar2(50));  2

Table created.

SQL>
SQL> insert into DENY_ACCESS values ('CONSULTA');

1 row created.

SQL> create table blocked_access(
ID_CURSO number(20)
);  2    3

Table created.

SQL> insert into blocked_access values (10);

1 row created.

SQL> insert into blocked_access values (30);

1 row created.

SQL> commit;

Commit complete.
SQL> grant select on deny_access to consulta;

Grant succeeded.

SQL> grant select on blocked_access  to consulta;

Grant succeeded.
SQL>
Realizada a criação das tabelas, estaremos criando agora a função que será associada a politica para restrição de acesso. Esta função irá basicamente comparar se o usuário da sessão conectada está cadastrado na blocked_access. Caso o usuário esteja cadastrado, ele cairá na condição que restringe o acesso ao ID_CURSO diferente dos ID_CURSO cadastrados na tabela DENY_ACCESS.
SQL> CREATE OR REPLACE FUNCTION VPD_DENY_ACCESS_NOTAS
(schema in varchar2, table_p in varchar2) return varchar2 as
  2    3  w_exists number :=0;
  4    predicate  varchar2(100) default null;
  5    BEGIN
  6    select max(vlr) into w_exists
  7    from (select 0 vlr
  8            from dual
  9          union
 10          select 1 vlr
 11            from lamim.deny_access
 12           where nome = UPPER(SYS_CONTEXT('USERENV', 'SESSION_USER')));
   IF w_exists = 1
 13   14      THEN
 15        predicate := 'ID_CURSO not in (select ID_CURSO from lamim.blocked_access)';
 16     ELSE
 17        predicate := '1=1';
 18    END IF;
 19    RETURN predicate;
END;
 20   21  /

Function created.
SQL>
Após a criação da função, devemos realizar a criação da politica. A documentação da politica pode ser verificada aqui.
SQL> BEGIN
 DBMS_RLS.ADD_POLICY (
  2    3    object_schema    => 'LAMIM',
  4    object_name      => 'NOTAS',
  5    policy_name      => 'VPD_DENY_ACCESS_NOTAS',
  6    function_schema  => 'LAMIM',
  7    policy_function  => 'VPD_DENY_ACCESS_NOTAS',
  8    statement_types  => 'SELECT, INSERT, UPDATE,DELETE');
  9  END;
 10  /

PL/SQL procedure successfully completed.

SQL>
Assim que a política é criada, nosso VPD já está ativo. Para demonstrar o funcionamento, estarei criando o usuário teste, que não será incluido no bloqueio. Desta forma, em nosso teste, o usuário consulta deverá visualizar todos os dados da tabela consulta, desde que o ID_CURSO seja diferente de 10 e 30 e o usuário teste, conseguirá ver normalmente.
SQL> create user teste identified by teste;

User created.

SQL> grant create session to teste;

Grant succeeded.

SQL> grant select on lamim.notas to teste;

Grant succeeded.

SQL> conn teste/teste
Connected.
SQL> show user;
USER is "TESTE"
SQL> set lines 150
SQL> col NOME_ALUNO for a10
SQL> select * from lamim.notas;

  ID_ALUNO   ID_CURSO NOME_ALUNO       NOTA
---------- ---------- ---------- ----------
         1         15 Scot                8
         2         15 Smith             6,5
         3         25 Tiger             3,3
         4         25 Sarah             4,7
         5         25 Jessie             10
         7         30 Roth              9,3
         8         30 John               10
         9         10 Allen             7,7
        10         10 Paul              5,1

9 rows selected.

--COM O USUÁRIO TESTE, TEMOS ACESSO A TODOS OS REGISTROS

--VAMOS CONECTAR COM O USUARIO RESTRITO NO VPD - USUARIO CONSULTA

SQL> conn consulta/consulta
Connected.
SQL> show user;
USER is "CONSULTA"
SQL> set lines 150
SQL> col NOME_ALUNO for a10
SQL> select * from lamim.notas;

  ID_ALUNO   ID_CURSO NOME_ALUNO       NOTA
---------- ---------- ---------- ----------
         2         15 Smith             6,5
         1         15 Scot                8
         5         25 Jessie             10
         4         25 Sarah             4,7
         3         25 Tiger             3,3

SQL>
-- COM O USUARIO CONSULTA, SAO LITADOS APENAS OS CURSOS DE ID_CURSO 15 E 25. 

--MOSTRANDO QUE NOSSA POLITICA FUNCIONOU CONFORME PLANEJADO.
SQL>
Em um próximo post, estarei abordando a criação de uma política a nível de coluna.

Fonte:
http://www.dba-oracle.com/t_dbms_rls.htm
https://uhesse.com/2013/06/18/fine-grained-access-control-with-dbms_rls-using-update_checktrue/
https://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_rls.htm
https://docs.oracle.com/cd/B28359_01/network.111/b28531/vpd.htm

Comentários