Além do sistema de privilégios padrão do SQL, disponível por meio do comando GRANT, as tabelas podem ter políticas de segurança de linha que restringem, por usuário, quais linhas podem ser retornadas por consultas comuns, ou inseridas, atualizadas ou excluídas por comandos de modificação de dados. Esse recurso também é conhecido como Segurança no Nível de Linha. Por padrão, as tabelas não possuem políticas, de modo que, se um usuário tiver privilégios de acesso a uma tabela conforme o sistema de privilégios do SQL, todas as linhas da tabela estarão igualmente disponíveis para consulta ou atualização.
Quando a segurança da tabela está ativada no nível de linha
(pelo comando
ALTER TABLE ... ENABLE ROW LEVEL SECURITY),
todo acesso usual à tabela para selecionar ou modificar linhas deve
ser permitido por uma política de segurança de linha. (No entanto,
normalmente o dono da tabela não está sujeito a políticas de
segurança de linha.) Se nenhuma política existir para a tabela, a
política de negação padrão será usada, significando que nenhuma
linha estará visível, ou poderá ser modificada. As operações que se
aplicam a toda a tabela, como TRUNCATE e
REFERENCES, não estão sujeitas à segurança de linha.
As políticas de segurança de linha podem ser específicas para comandos,
funções de banco de dados (roles), ou ambos.
Uma política pode ser especificada para ser aplicada a todos os comandos
(ALL), ou a SELECT,
INSERT, UPDATE ou
DELETE. Podem ser atribuídas várias
funções de banco de dados a uma determinada política,
e as regras normais de associação e herança são aplicadas.
Para especificar quais linhas são visíveis ou modificáveis de acordo
com uma política, é necessária uma expressão que retorne um resultado
booleano. Esta expressão será avaliada para cada linha antes de
quaisquer condições ou funções provenientes da consulta do usuário.
(As únicas exceções a esta regra são as funções
à prova de vazamento, que garantem que não vão
vazar informações; o otimizador pode optar por aplicar essas
funções antes da verificação de segurança de linha). Linhas para as
quais a expressão não retorna verdade não
são processadas. Podem ser especificadas expressões separadas para
proporcionar controle independente sobre as linhas que podem ser vistas
e as linhas que podem ser modificadas. As expressões de política
são executadas como parte da consulta e com os privilégios do usuário
que executa a consulta, embora as funções do definidor de segurança
possam ser usadas para acessar dados não disponíveis ao usuário que
faz a chamada.
Superusuários e funções de banco de dados
(roles) com o atributo
BYPASSRLS sempre ignoram o sistema de segurança
de linha ao acessar uma tabela. Normalmente a segurança de linha
também é ignorada para os donos das tabelas, embora o dono da
tabela possa optar por estar sujeito à segurança de linha usando
ALTER TABLE ... FORCE ROW LEVEL SECURITY.
Ativar e desativar a segurança de linha, assim como adicionar políticas a uma tabela, é sempre um privilégio apenas do dono da tabela.
As políticas são criadas usando o comando CREATE POLICY, modificadas usando o comando ALTER POLICY, e excluídas usando o comando DROP POLICY. Para ativar e desativar a segurança de linha para uma determinada tabela é usado o comando ALTER TABLE.
Cada política tem seu nome, podendo ser definidas várias políticas para uma tabela. Como as políticas são específicas da tabela, cada política da tabela deve ter um nome único. Tabelas diferentes podem ter políticas com o mesmo nome.
Quando são aplicadas várias políticas a uma mesma consulta, elas são
combinadas usando OR (para políticas permissivas,
que são o padrão) ou usando AND (para políticas
restritivas). Isso é semelhante à regra de que uma determinada função
tem os privilégios de todas as roles
das quais é membro. Políticas permissivas versus restritivas são
discutidas mais adiante.
Como um exemplo simples, aqui está como criar uma política na tabela
contas para permitir que apenas os membros da
função de banco de dados (role)
gerentes acessem as linhas, e somente as linhas
de suas contas:
CREATE TABLE contas (gerente text, empresa text, email_contato text);
ALTER TABLE contas ENABLE ROW LEVEL SECURITY;
-- A função de banco de dados gerentes precisa estar criada
-- antes de executar o comando abaixo
CREATE POLICY gerentes_das_contas ON contas TO gerentes
USING (gerente = current_user);
A política acima produz, implicitamente, uma cláusula
WITH CHECK idêntica à sua cláusula
USING, de modo que a restrição se aplica tanto
às linhas selecionadas por um comando (assim, um gerente não pode
usar SELECT, UPDATE ou
DELETE nas linhas existentes pertencentes a outro
gerente) quanto às linhas modificadas por um comando (assim, as
linhas pertencentes a outro gerente não podem ser criadas via
INSERT ou UPDATE).
Se nenhuma função de banco de dados (role)
for especificada, ou o nome especial de usuário PUBLIC
for usado, então a política será aplicada a todos os usuários do sistema.
Para permitir que todos os usuários acessem apenas a sua própria linha
na tabela usuarios, pode ser usada uma
política simples:
CREATE POLICY politica_de_usuario ON usuarios
USING (nome_do_usuario = current_user);
Isso funciona de forma semelhante ao exemplo anterior.
Para usar políticas diferentes para as linhas adicionadas à tabela
e para as linhas visíveis, podem ser combinadas múltiplas políticas.
O par de políticas abaixo permite que todos os usuários visualizem
todas as linhas da tabela usuarios,
mas só permite modificar as suas próprias linhas:
CREATE POLICY politica_seleciona_usuario ON usuarios
FOR SELECT
USING (true);
CREATE POLICY politica_modifica_usuario ON usuarios
USING (nome_do_usuario = current_user);
Em um comando SELECT essas duas políticas são
combinadas usando OR, com o efeito final de que
todas as linhas podem ser selecionadas. Em outros tipos de comando,
apenas a segunda política se aplica, para que os efeitos sejam os
mesmos de antes.
A segurança de linha também pode ser desativada pelo comando
ALTER TABLE. A desativação da segurança de linha
não remove nenhuma política definida na tabela; elas são simplesmente
ignoradas. Depois disso, todas as linhas da tabela se tornam visíveis
e modificáveis, sujeitas ao sistema de privilégios padrão do
SQL.
Abaixo está um exemplo maior de como as políticas podem ser usadas em
ambientes de produção. A tabela passwd emula um
arquivo de senha do Unix:
-- Exemplo simples baseado no arquivo passwd
CREATE TABLE passwd (
user_name text UNIQUE NOT NULL,
pwhash text,
uid int PRIMARY KEY,
gid int NOT NULL,
real_name text NOT NULL,
home_phone text,
extra text,
home_dir text NOT NULL,
shell text NOT NULL
);
CREATE ROLE admin; -- Administrador
CREATE ROLE bob; -- Usuário comum
CREATE ROLE alice; -- Usuário comum
-- Inserir os dados na tabela
INSERT INTO passwd VALUES
('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/dash');
INSERT INTO passwd VALUES
('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/zsh');
INSERT INTO passwd VALUES
('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/bin/zsh');
-- Certifique-se de ativar a segurança ao nível de linha na tabela
ALTER TABLE passwd ENABLE ROW LEVEL SECURITY;
-- Criar as políticas
-- O administrador pode ver todas as linhas e adicionar qualquer linha
CREATE POLICY admin_tudo ON passwd TO admin USING (true) WITH CHECK (true);
-- Os usuários comuns podem visualizar todas as linhas
CREATE POLICY todas_vistas ON passwd FOR SELECT USING (true);
-- Os usuários comuns podem atualizar seus próprios registros, mas
-- estão limitados com relação a quais shells podem definir
CREATE POLICY modif_usuario ON passwd FOR UPDATE
USING (current_user = user_name)
WITH CHECK (
current_user = user_name AND
shell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/tcsh')
);
-- Conceder todos os privilégios para o administrador
GRANT SELECT, INSERT, UPDATE, DELETE ON passwd TO admin;
-- Os usuários comuns só têm acesso às colunas públicas
GRANT SELECT
(user_name, uid, gid, real_name, home_phone, extra, home_dir, shell)
ON passwd TO public;
-- Permitir os usuário comuns atualizarem algumas colunas
GRANT UPDATE
(pwhash, real_name, home_phone, extra, shell)
ON passwd TO public;
Como acontece com qualquer configuração de segurança, é importante testar para garantir que o sistema esteja se comportando conforme o desejado. Usando o exemplo acima, o material visto a seguir mostra que o sistema de permissão está funcionando corretamente.
-- O Administrador pode ver todos os campos e linhas da tabela postgres=> set role admin; SET postgres=> table passwd;user_name | pwhash | uid | gid | real_name | home_phone | extra | home_dir | shell -----------+--------+-----+-----+-----------+--------------+-------+-------------+----------- admin | xxx | 0 | 0 | Admin | 111-222-3333 | | /root | /bin/dash bob | xxx | 1 | 1 | Bob | 123-456-7890 | | /home/bob | /bin/zsh alice | xxx | 2 | 1 | Alice | 098-765-4321 | | /home/alice | /bin/zsh (3 linhas)-- Verificar o que a Alice pode fazer postgres=> set role alice; SET postgres=> table passwd; ERRO: permissão negada para tabela passwd postgres=> select user_name,real_name,home_phone,extra,home_dir,shell from passwd;user_name | real_name | home_phone | extra | home_dir | shell -----------+-----------+--------------+-------+-------------+----------- admin | Admin | 111-222-3333 | | /root | /bin/dash bob | Bob | 123-456-7890 | | /home/bob | /bin/zsh alice | Alice | 098-765-4321 | | /home/alice | /bin/zsh (3 linhas)postgres=> update passwd set user_name = 'joe'; ERRO: permissão negada para tabela passwd -- Alice tem permissão para alterar seu próprio nome verdadeiro, mas de nenhum outro postgres=> update passwd set real_name = 'Alice Doe'; UPDATE 1 postgres=> update passwd set real_name = 'John Doe' where user_name = 'admin'; UPDATE 0 postgres=> update passwd set shell = '/bin/xx'; ERRO: a nova linha viola a política de segurança no nível de linha da tabela "passwd" postgres=> delete from passwd; ERRO: permissão negada para tabela passwd postgres=> insert into passwd (user_name) values ('xxx'); ERRO: permissão negada para tabela passwd -- Alice pode alterar sua própria senha; -- A segurança no nível de linha (RLS) -- impede silenciosamente a atualização de outras linhas postgres=> update passwd set pwhash = 'abc'; UPDATE 1
Todas as políticas criadas até agora foram políticas permissivas,
significando que, quando várias políticas são aplicadas, elas são
combinadas usando o operador booleano “OR”.
Embora as políticas permissivas possam ser construídas para permitir
apenas o acesso às linhas nos casos pretendidos, pode ser mais simples
combinar políticas permissivas com políticas restritivas (nas quais
os registros devem passar, e são combinadas usando o operador
booleano “AND”).
Baseado no exemplo acima, adicionamos uma política restritiva para
exigir que o administrador esteja conectado por meio de um
soquete Unix local para acessar os registros da tabela
passwd:
CREATE POLICY somente_admin_local ON passwd AS RESTRICTIVE TO admin
USING (pg_catalog.inet_client_addr() IS NULL);
Abaixo podemos ver que o administrador conectado por meio de TCP/IP não tem acesso a nenhum registro, devido à política restritiva:
=> SELECT current_user;current_user -------------- admin (1 linha)=> select inet_client_addr();inet_client_addr ------------------ 127.0.0.1 (1 linha)=> TABLE passwd;user_name | pwhash | uid | gid | real_name | home_phone | extra | home_dir | shell -----------+--------+-----+-----+-----------+------------+-------+----------+------- (0 linhas)=> UPDATE passwd set pwhash = NULL; UPDATE 0
As verificações de integridade referencial, como restrições de unicidade ou de chave primária e referências de chave estrangeira, sempre ignoram a segurança de linha para garantir que a integridade dos dados seja mantida. Deve-se tomar cuidado ao desenvolver esquemas e políticas no nível de linha para evitar vazamentos de informações de “canal encoberto” [35] por meio das verificações de integridade referencial.
Em alguns contextos é importante ter certeza de que a segurança de
linha não está sendo aplicada. Por exemplo, ao se fazer uma cópia de
segurança pode ser desastroso se a segurança de linha fizer
silenciosamente com que algumas linhas sejam omitidas.
Nessa situação, pode ser definido o parâmetro de configuração
row_security como off.
Isso por si só não ignora a segurança de linha; o que faz é gerar
um erro se os resultados de qualquer consulta forem filtrados por
uma política.
O motivo do erro pode então ser investigado e corrigido.
Nos exemplos acima, as expressões contidas nas políticas consideram
apenas os valores correntes das linhas a serem acessadas ou modificadas.
Esse é o caso mais simples e de melhor desempenho; quando for
possível, é melhor projetar aplicações de segurança de linha para
funcionarem dessa forma.
Se for necessário consultar outras linhas, ou outras tabelas, para
tomar uma decisão de política, isso pode ser feito usando
sub‑SELECTs, ou funções que contenham
SELECTs, nas expressões de políticas.
Esteja ciente, no entanto, de que tais acessos podem criar condições
de concorrência que podem permitir o vazamento de informações se
não houver cuidado.
Como exemplo, considere o seguinte projeto de tabela:
-- definição de grupos de privilégios
CREATE TABLE grupos (id_grupo int PRIMARY KEY,
nome_grupo text NOT NULL);
INSERT INTO grupos VALUES
(1, 'baixo'),
(2, 'médio'),
(5, 'alto');
GRANT ALL ON grupos TO alice; -- Alice é a administradora
GRANT SELECT ON grupos TO public;
-- definição dos níveis de privilégio dos usuários
CREATE TABLE usuarios (nome_usuario text PRIMARY KEY,
id_grupo int NOT NULL REFERENCES grupos);
INSERT INTO usuarios VALUES
('alice', 5),
('bob', 2),
('mallory', 2);
GRANT ALL ON usuarios TO alice;
GRANT SELECT ON usuarios TO public;
-- tabela contendo a informação a ser protegida
CREATE TABLE informacoes (informacao text,
id_grupo int NOT NULL REFERENCES grupos);
INSERT INTO informacoes VALUES
('quase secreto', 1),
('ligeiramente secreto', 2),
('muito secreto', 5);
ALTER TABLE informacoes ENABLE ROW LEVEL SECURITY;
-- a linha deve ser visível/atualizável pelos usuários cujo id_grupo
-- de segurança seja maior ou igual ao id_grupo da linha
CREATE POLICY fp_s ON informacoes FOR SELECT
USING (id_grupo <= (SELECT id_grupo FROM usuarios WHERE nome_usuario = current_user));
CREATE POLICY fp_u ON informacoes FOR UPDATE
USING (id_grupo <= (SELECT id_grupo FROM usuarios WHERE nome_usuario = current_user));
-- contamos apenas com segurança a nível de linha (RLS)
-- para proteger a tabela informacoes
GRANT ALL ON informacoes TO public;
Agora, suponha que alice queira mudar a informação
“ligeiramente secreto”, mas acha que
mallory não deve poder ver o novo conteúdo
dessa linha, então ela faz:
BEGIN; UPDATE usuarios SET id_grupo = 1 WHERE nome_usuario = 'mallory'; UPDATE informacoes SET informacao = 'escondida da mallory' WHERE id_grupo = 2; COMMIT;
Isso parece seguro; não há nenhuma janela por onde
mallory possa ver o texto
“escondida da mallory”.
No entanto, há uma condição de concorrência aqui.
Se mallory estiver fazendo simultaneamente, digamos,
SELECT * FROM informacoes WHERE id_grupo = 2 FOR UPDATE;
e sua transação está no modo READ COMMITTED, é
possível que mallory veja
“escondida da mallory”.
Isso vai acontecer se a transação da mallory
atingir a linha da tabela informações logo
após a transação da alice.
A transação da mallory vai ficar bloqueada
aguardando a transação da alice ser confirmada e,
então, busca o conteúdo da linha atualizada graças à cláusula
FOR UPDATE.
No entanto, ela não busca a linha atualizada
para o SELECT implícito da tabela
usuarios, porque esse
sub‑SELECT não tem FOR UPDATE;
em vez disso, a linha da tabela usuarios
é lida com o instantâneo obtido no início da consulta. Portanto, a
expressão de política testa o valor antigo do nível de privilégio da
mallory, permitindo que ela veja a linha atualizada.
Existem várias maneiras de contornar esse problema. Uma maneira
simples é usar SELECT ... FOR SHARE em
sub‑SELECTs nas políticas de segurança de linha.
No entanto, isso requer a concessão do privilégio
UPDATE na tabela referenciada (aqui
usuarios) aos usuários afetados,
o que pode ser indesejável. (Mas outra política de segurança de linha
pode ser aplicada para impedir que eles realmente exerçam esse
privilégio; ou o sub‑SELECT pode ser incorporado
a uma função definidora de segurança.) Além disso, o uso simultâneo
pesado de bloqueios de compartilhamento de linha na tabela referenciada
pode representar um problema de desempenho, principalmente se as
atualizações forem frequentes. Outra solução prática, se as
atualizações da tabela referenciada forem pouco frequentes, é usar um
bloqueio ACCESS EXCLUSIVE na tabela referenciada
ao atualizá-la, para que nenhuma transação simultânea possa examinar
valores de linha antigos. Ou pode-se apenas esperar que todas as
transações simultâneas terminem após efetivar a atualização da
tabela referenciada e antes de fazer alterações que dependam da nova
situação de segurança.
Para obter detalhes adicionais veja CREATE POLICY e ALTER TABLE.
As políticas de segurança no nível de linha no banco de dados podem ser vistas consultando a visão do sistema Seção 53.15.
[35] covert channel: Um canal intra-sistema não intencional ou não autorizado permitindo que duas entidades cooperantes transfiram informações em uma forma que viole a política de segurança do sistema, mas não exceda as autorizações de acesso das entidades. (N. T.)