Escolha da abordagem de autenticação para aplicativo financeiro no PostgreSQL

15

Primeiro alguns antecedentes.

O projeto LedgerSMB é um projeto de software de contabilidade financeira de código aberto executado no PostgreSQL. Implementamos uma quantidade muito grande de lógica de negócios em funções definidas pelo usuário, que atuam como a principal ferramenta de mapeamento entre os métodos de objeto do programa e o comportamento do banco de dados. Atualmente, usamos usuários de banco de dados como usuários de autenticação, em parte por opção (isso permite lógica de segurança centralizada, para que outras ferramentas possam ser gravadas e reutilizadas permissões concedidas aos usuários), e em parte por necessidade (depois de sairmos do SQL-Ledger, lá não havia muitas opções para adaptar a segurança a essa base de código).

Isso nos permite acessar um número razoável de opções de conexão única às quais o PostgreSQL tem acesso, do LDAP ao Kerberos 5. Podemos até usar o PAM no que diz respeito às senhas. Também nos permite reutilizar permissões ao integrar com outros aplicativos ou permitir outras interfaces de cliente. Para um aplicativo de contabilidade financeira, isso parece uma vitória líquida.

Existem custos óbvios envolvidos. Para o aplicativo Web, estamos muito limitados aos tipos de autenticação http que podem ser suportados. DIGEST, por exemplo, está totalmente fora. O BASIC funciona, e poderíamos implementar o KRB5 com bastante facilidade (planejo ter esse suporte e trabalhar pronto para o 1.4). Medidas de autenticação muito fortes não podem ser gerenciadas adequadamente diretamente, embora possamos provavelmente colocá-las em prática, se necessário (por exemplo, certificado SSL BASIC + do lado do cliente com um cn correspondente ao nome do usuário e uma raiz específica ca).

Ao mesmo tempo, recebemos muitas críticas principalmente da multidão de desenvolvedores e, mais ocasionalmente, dos dbas que me dizem que o aplicativo deve ser a barreira de segurança, não o banco de dados. Minha opinião ainda é que um perímetro de segurança menor geralmente é melhor, que a reutilização da lógica de negócios e da lógica de segurança andam juntas e que me parece perigoso reutilizar a lógica de negócios sem reutilizar a lógica de segurança no mesmo nível do programa.

Estou perdendo algumas grandes compensações aqui? Existem truques que eu não estou considerando?

Chris Travers
fonte
1
Postado na lista de discussão pgsql-general. Veja o tópico que começa aqui .
Craig Ringer

Respostas:

17

Acho que você está confundindo autenticação e autorização .

Concordo plenamente que é prudente manter o modelo de segurança no banco de dados, especialmente porque o LedgerSMB foi projetado com o acesso de vários clientes em mente. A menos que você planeje usar três camadas com uma camada de middleware, faz todo sentido ter usuários como funções de banco de dados, especialmente para algo como um aplicativo de contabilidade.

Isso não significa que você precise autenticar usuários no banco de dados usando um método de autenticação suportado pelo PostgreSQL. Os usuários, funções e concessões do banco de dados podem ser usados ​​para autorização somente se você desejar.

Veja como funciona para uma interface de usuário da web, por exemplo:

  • janeconecta-se ao servidor da interface do usuário da web e se autentica usando qualquer método desejado, como handshake de certificado de cliente HTTPS X.509 e autenticação DIGEST. O servidor agora tem uma conexão de um usuário que aceita realmente jane.

  • O servidor se conecta ao PostgreSQL usando um nome de usuário / senha fixo (ou Kerberos ou o que você quiser), autenticando-se no servidor db como usuário webui. O servidor db confia webuipara autenticar seus usuários e, portanto webui, recebe GRANTs apropriados (veja abaixo).

  • Nessa conexão, o servidor usa SET ROLE jane;para assumir o nível de autorização do usuário jane. Até que RESET ROLE;outro SET ROLEseja executado, a conexão está operando com os mesmos direitos de acesso que janee SELECT current_user()etc serão relatados jane.

  • O servidor mantém a associação entre a conexão do banco de dados em que se tem SET ROLEa janeea sessão web para o usuário jane, não permitindo que a conexão PostgreSQL para ser usado por outras conexões com outros usuários sem um novo SET ROLEinbetween.

Agora você está autenticando fora do servidor, mas mantendo a autorização no servidor. A Pg precisa saber quais usuários existem, mas não precisa de senhas ou métodos de autenticação para eles.

Vejo:

Detalhes

O servidor webui controla a execução das consultas e não permitirá a janeexecução de SQL bruto (espero!), Por isso janenão é possível RESET ROLE; SET ROLE special_admin_user;através da interface do usuário da web. Para maior segurança, eu adicionaria um filtro de declaração ao servidor que rejeitou SET ROLEe a RESET ROLEmenos que a conexão estivesse dentro ou entrando em um conjunto de conexões não atribuídas.

Você ainda pode usar a autenticação direta à página em outros clientes; você pode misturar e combinar livremente. Você apenas tem que GRANTo webuiusuário os direitos para SET ROLEa usuários que podem fazer login através da web e, em seguida, dar esses usuários quaisquer normais CONNECTdireitos, senhas, etc que você deseja. Se você deseja torná-los somente para a Web, REVOKEseus CONNECTdireitos no banco de dados (e de public).

Para facilitar essa divisão de autenticação / autorização, tenho uma função especial para a assume_any_userqual GRANTtodos os usuários recém-criados. Depois, passo GRANT assume_any_userpara o nome de usuário real usado por coisas como um front-end da web confiável, dando a eles o direito de se tornarem qualquer usuário que eles gostem.

É importante fazer assume_any_useruma NOINHERITfunção, para que o webuiusuário ou qualquer outra coisa não tenha privilégios e só possa atuar no banco de dados quando for SET ROLEpara um usuário real. Sob nenhuma circunstância deve webuiser um superusuário ou proprietário do banco de dados .

Se você for um pool de conexões, poderá SET LOCAL ROLEdefinir apenas a função dentro de uma transação, para poder retornar as conexões ao pool após COMMITou ROLLBACK. Cuidado com o que RESET ROLEainda funciona, por isso ainda não é seguro deixar o cliente executar o SQL que quiser.

SET SESSION AUTHORIZATIONé a versão relacionada, mas mais forte, deste comando. Não requer associação de função, mas é um comando apenas de superusuário. Você não deseja que a interface do usuário da web se conecte como superusuário. Ele pode ser revertida com RESET SESSION AUTHORIZATION, SET SESSION AUTHORIZATION DEFAULTou SET SESSION AUTHORIZATION theusernamepara recuperar direitos de superusuário por isso não é uma barreira de segurança de cair o privilégio quer.

Um comando que funcionasse como SET SESSION AUTHORIZATIONfosse irreversível e funcionaria se você fosse um membro da função, mas não um superusuário, seria ótimo. Neste ponto, não há um, mas você ainda pode separar a autenticação e a autorização muito bem se tomar cuidado.

Exemplo e explicação

CREATE ROLE dbowner NOLOGIN;
CREATE TABLE test_table(x text);
INSERT INTO test_table(x) VALUES ('bork');
ALTER TABLE test_table OWNER TO dbowner;

CREATE ROLE assume_any_user NOINHERIT NOLOGIN;
CREATE ROLE webui LOGIN PASSWORD 'somepw' IN ROLE assume_any_user;

CREATE ROLE jane LOGIN PASSWORD 'somepw';
GRANT jane TO assume_any_user;
GRANT ALL ON TABLE test_table TO jane;

CREATE ROLE jim LOGIN PASSWORD 'somepw';
GRANT jim TO assume_any_user;

Agora conecte como webui. Note que você não pode fazer nada para test_tablemas você pode SET ROLE para janee , em seguida, você pode acessar test_table:

$ psql -h 127.0.0.1 -U webui regress
Password for user webui:

regress=> SELECT session_user, current_user;
 session_user | current_user 
--------------+--------------
 webui        | webui
(1 row)



regress=> SELECT * FROM test_table;
ERROR:  permission denied for relation test_table

regress=> SET ROLE jane;
SET

regress=> SELECT session_user, current_user;
 session_user | current_user 
--------------+--------------
 webui        | jane
(1 row)

regress=> SELECT * FROM test_table;
  x   
------
 bork
(1 row)

Note-se que webui lata SET ROLE para jim, mesmo quando já SET ROLEd para janee mesmo que janenão tenha sido GRANTed o direito de assumir o papel jim. SET ROLEdefine seu ID de usuário efetivo, mas não remove sua capacidade para SET ROLEoutras funções, isso é propriedade da função que você conectou e não da sua função efetiva atual. Conseqüentemente, você deve controlar cuidadosamente o acesso aos comandos SET ROLEe RESET ROLE. No AFAIK, não há como permanecer permanentemente SET ROLEem uma conexão, realmente se tornando o usuário alvo, embora certamente seja bom ter isso.

Comparar:

$ psql -h 127.0.0.1 -U webui regress
Password for user webui:

regress=> SET ROLE jane;
SET

regress=> SET ROLE jim;
SET
regress=> SELECT session_user, current_user;
 session_user | current_user 
--------------+--------------
 webui        | jim
(1 row)

para:

$ psql -h 127.0.0.1 -U jane regress
Password for user jane:

regress=> SET ROLE webui;
ERROR:  permission denied to set role "webui"
regress=> SET ROLE jim;
ERROR:  permission denied to set role "jim"

Isso significa que SET ROLEnão é exatamente o mesmo que fazer login como uma determinada função, algo que você deve ter em mente.

webuinão pode SET ROLEde dbowneruma vez que não foi GRANTed certo:

regress=> SET ROLE dbowner;
ERROR:  permission denied to set role "dbowner"

portanto, por si só, é bastante impotente, só pode assumir os direitos de outros usuários e somente quando esses usuários têm acesso à Web ativado.

Craig Ringer
fonte
1
btw, você pode querer ver como pgbouncerfunciona para alguns detalhes.
Craig Ringer
2
Ah, DISCARD ALLé outra maneira de os direitos voltarem ao padrão. Eu realmente gostaria que a Pg tivesse um SET ROLE NORESETou algo semelhante ...
Craig Ringer