Oracle Virtual Private Database – VPD

O Virtual Private Database, mais conhecido como VPD, é um recurso do Oracle Database Enterprise Edition (EE) existente desde a versão 8i que permite criar políticas de segurança simples ou complexas sobre objetos, sejam eles tabelas, views ou sinônimos.

O VPD é usado quando os privilégios de objeto padrão e funções de banco de dados associados são insuficientes para atender às exigências de segurança do aplicativo, deste modo, políticas personalizadas a nível de linha e/ou coluna (a partir da versão 10g) podem limitar o acesso a informações restritas, por exemplo.

Como as políticas de segurança estão associadas a um objeto (tabela, view ou sinônimo) dentro do database, sempre que ocorrer o acesso ao objeto elas são automaticamente acionadas independente da fonte (por meio de um aplicativo, uma interface Web ou SQL * Plus) proporcionando sempre o mesmo resultado e impedindo a violação da segurança.

Ao acessar o objeto protegido com o Oracle Virtual Private Database policy, o Oracle Database dinamicamente modifica o comando SQL do usuário. A modificação cria a condição WHERE (caso não exista na SQL) e retorna a função de segurança implementada de forma totalmente transparente para o usuário.

Podemos aplicar com o VPD políticas de segurança sobre sentenças como SELECT, INSERT, UPDATE, INDEX e DELETE.

Neste artigo irei demonstrar a criação de uma VPD simples a nível de linha ( Row Level Security), ou seja, limitando usuários apenas a não poderem visualizar determinados registros em um tabela.

Criando e populando uma tabela FUNCIONARIO onde vou posteriormente aplicar a policy.

SQL> create table funcionario(
  2  nome varchar2(50),
  3  salario number(10,2)
  4  );

Table created.

SQL> insert into funcionario values ('FUNC1',1000.00);

1 row created.

SQL> insert into funcionario values ('FUNC2',1000.00);

1 row created.

SQL> insert into funcionario values ('FUNC3',2000.00);

1 row created.

SQL> insert into funcionario values ('FUNC4',5000.00);

1 row created.

SQL> insert into funcionario values ('FUNC5',10000.50);

1 row created.

SQL> commit;

Commit complete.

Criando a tabela DENY_ACCESS_FUN onde vou cadastrar os usuários que serão afetados(limitados) pela policy.

SQL> create table deny_access_fun(
  2  nome varchar2(50)
  3  );

Table created.

Criando a tabela BLOCKED_ACCESS_FUN onde vou cadastrar o nome dos registros da tabela FUNCIONARIO que não podem ser visualizados pelos usuários cadastrados na tabela DENY_ACCESS_FUN.

SQL> create table blocked_access_fun(
  2  nome varchar2(50)
  3  );

Table created.

Agora vou criar a FUNÇÃO que será chamada pela policy e que irá retornar a condição (PREDICATE) para a cláusula WHERE, ou seja, quando um usuário cadastrado na tabela DENY_ACCESS_FUN realizar um select na tabela FUNCIONARIO a policy vai atribuir na condição WHERE a linha abaixo:

nome not in (select nome from blocked_access_fun);

Se o usuário não está cadastrado na DENY_ACCESS_FUN ela retorna: 1=1 o que não afeta nenhum registro.

SQL> CREATE OR REPLACE FUNCTION VPD_DENY_ACCESS_FUN
  2  (schema in varchar2, table_p in varchar2) return varchar2 as
  3  w_exists number :=0;
  4    predicate  varchar2(50) 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 deny_access_fun
 12           where nome = UPPER(SYS_CONTEXT('USERENV', 'SESSION_USER')));
 13     IF w_exists = 1
 14      THEN
 15        predicate := 'nome not in (select nome from blocked_access_fun)';
 16     ELSE
 17        predicate := '1=1';
 18    END IF;
 19    RETURN predicate;
 20  END;
 21  /

Function created.

Observem que na FUNCTION utilizei a SYS_CONTEXT para coletar informações da minha sessão, no caso o usuário que conectei (SESSION_USER). Você pode saber mais sobre a SYS_CONTEXT acessando o artigo, SYS_CONTEXT ‘USERENV’.

Antes de criar a policy vamos aplicar o seguinte grant:

SQL> GRANT EXECUTE ON DBMS_RLS TO PUBLIC;

Grant succeeded.

Criando a policy:

SQL> BEGIN
  2   DBMS_RLS.ADD_POLICY (
  3    object_schema    => 'ANDERSON',
  4    object_name      => 'FUNCIONARIO',
  5    policy_name      => 'VPD_DENY_ACCESS_FUN_RH',
  6    function_schema  => 'ANDERSON',
  7    policy_function  => 'VPD_DENY_ACCESS_FUN',
  8    statement_types  => 'select');
  9  END;
 10  /

PL/SQL procedure successfully completed.

Observe que na statement_types coloquei apenas SELECT, desta forma a policy só é executada quando feito um select na tabela FUNCIONARIO.

Criando 2 usuários para testes e aplicando as permissões necessárias:

SQL> create user teste1 identified by andersonteste1;

User created.

SQL> create user teste2 identified by andersonteste2;

User created.

SQL> grant create session to teste1, teste2;

Grant succeeded.

SQL> grant select on anderson.funcionario to teste1, teste2;

Grant succeeded.

Agora conectando com os usuários criados podemos ver que os registros estão todos sendo mostrados porque ainda não cadastrei nada nas tabelas DENY_ACCESS_FUN e BLOCKED_ACCESS_FUN:

SQL> conn teste1/andersonteste1
Connected.
SQL> select * from anderson.funcionario;

NOME                                                  SALARIO
-------------------------------------------------- ----------
FUNC1                                                    1000
FUNC2                                                    1000
FUNC3                                                    2000
FUNC4                                                    5000
FUNC5                                                 10000.5

SQL> conn teste2/andersonteste2
Connected.
SQL> select * from anderson.funcionario;

NOME                                                  SALARIO
-------------------------------------------------- ----------
FUNC1                                                    1000
FUNC2                                                    1000
FUNC3                                                    2000
FUNC4                                                    5000
FUNC5                                                 10000.5

Em uma outra sessão (com permissão de insert nas tabelas), vou bloquear o usuário TESTE1 para não visualizar os registros do funcionário FUNC3:

SQL> insert into deny_access_fun values ('TESTE1');

1 row created.

SQL> insert into blocked_access_fun values ('FUNC3');

1 row created.

SQL> commit;

Commit complete.

SQL> conn teste1/andersonteste1
Connected.
SQL> select * from anderson.funcionario;

NOME                                                  SALARIO
-------------------------------------------------- ----------
FUNC1                                                    1000
FUNC2                                                    1000
FUNC4                                                    5000
FUNC5                                                 10000.5

Observe acima que o FUNC3 já não foi mais retornado para o usuário TESTE1 enquanto para o usuário TESTE2 ainda é apresentado:

SQL> conn teste2/andersonteste2
Connected.
SQL> select * from anderson.funcionario;

NOME                                                  SALARIO
-------------------------------------------------- ----------
FUNC1                                                    1000
FUNC2                                                    1000
FUNC3                                                    2000
FUNC4                                                    5000
FUNC5                                                 10000.5

Vou cadastrar mais uma linha(nome) da tabela FUNCIONARIO que não deve ser visualiza pelo usuário TESTE1:

SQL> conn anderson
Enter password:
Connected.
SQL> insert into blocked_access_fun values ('FUNC5');

1 row created.

SQL> commit;

Commit complete.

SQL> conn teste1/andersonteste1
Connected.
SQL> select * from anderson.funcionario;

NOME                                                  SALARIO
-------------------------------------------------- ----------
FUNC1                                                    1000
FUNC2                                                    1000
FUNC4                                                    5000

SQL> conn teste2/andersonteste2
Connected.
SQL> select * from anderson.funcionario;

NOME                                                  SALARIO
-------------------------------------------------- ----------
FUNC1                                                    1000
FUNC2                                                    1000
FUNC3                                                    2000
FUNC4                                                    5000
FUNC5                                                 10000.5

Agora cadastrando também o usuário TESTE2 na tabela DENY_ACCESS_FUN. Vejam que ele também não consegue mais ver os outros registros.

SQL> conn anderson
Enter password:
Connected.
SQL> insert into deny_access_fun values ('TESTE2');

1 row created.

SQL> commit;

Commit complete.

SQL> conn teste2/andersonteste2
Connected.
SQL> select * from anderson.funcionario;

NOME                                                  SALARIO
-------------------------------------------------- ----------
FUNC1                                                    1000
FUNC2                                                    1000
FUNC4                                                    5000

Como falado no inicio do artigo este foi apenas um exemplo simples de implementação com VPD limitando o SELECT a determinados USUÁRIOS X REGISTROS.

A VPD pode ser implementada de muitas outras formas e pode ser muito mais abrangente e complexa.

Outros exemplos e documentações podemos encontrar em:

Referências:

http://docs.oracle.com/cd/B28359_01/network.111/b28531/vpd.htm
http://www.oracle.com/technetwork/database/security/index-088277.html

%name Oracle Virtual Private Database   VPD

Autor: Anderson Graf

Bacharel em Sistemas de Informação e MBA em Gestão de Banco de Dados Oracle. Entusiasta da tecnologia Oracle, ACE Associate ♠, autor em vários blogs e OTN. Consultor Oracle Senior na Exímio Soluções em TI

Envolvido em soluções de:
– Implementação, migração, gerenciamento e suporte a produtos Oracle, multiplataforma
– Monitoramento de ambientes 24×7
– Backup e Recovery
– Performance e Tuning
– Alta disponibilidade (HA) – RAC, Data Guard
– EM database/grid/cloud control
– Particionamento & Advanced Compression
– Oracle Engineered Systems – ODA, Exadata

Blog pessoal: http://www.andersondba.com.br
Articulista na Oracle Technology Network (OTN) – https://goo.gl/99R6yW
ACE Associate – https://goo.gl/MBB51b
Articulista GPO – http://profissionaloracle.com.br