Avaliação dinâmica de código em Java - inteligente ou desleixado?

30

Estou tentando criar uma estrutura ACL flexível em Java para o meu aplicativo.

Muitas estruturas da ACL são criadas em uma lista de permissões, onde uma regra está na forma de owner: action: resource . Por exemplo,

  • "JOHN pode visualizar o recurso FOOBAR-1"
  • "MARY pode visualizar o recurso FOOBAR-1"
  • "MARY pode editar o recurso FOOBAR-1"

Isso é atraente porque as regras podem ser facilmente serializadas / persistidas em um banco de dados. Mas meu aplicativo tem lógica de negócios complexa. Por exemplo,

  • "Todos os usuários do departamento 1 com mais de 5 anos de antiguidade podem VISUALIZAR o recurso FOOBAR-1, caso contrário não estão autorizados"
  • "Todos os usuários do departamento 2, se a data for posterior a 15/03/2016, podem VER o recurso FOOBAR-2, caso contrário não autorizado"

Pensando bem, seria um pesadelo conceber um esquema de banco de dados que pudesse lidar com regras infinitamente complexas como essas. Portanto, parece que eu precisaria "inseri-los" no aplicativo compilado, avaliá-los para cada usuário e, em seguida, produzir regras owner: action: resource como resultado da avaliação. Eu quero evitar inserir a lógica no aplicativo compilado.

Então, eu estava pensando em representar uma regra na forma de predicado : action: resource , em que o predicado é uma expressão booleana que determina se um usuário é permitido. O predicado seria uma string de uma expressão JavaScript que poderia ser avaliada pelo mecanismo Rhino do Java. Por exemplo,

  • return user.getDept() == 1 && user.seniority > 5;

Ao fazer isso, os predicados podem ser facilmente persistidos no banco de dados.

Isso é inteligente ? Isso é desleixado ? Isso é enigmático ? Isso é excesso de engenharia ? Isso é seguro (aparentemente, o Java pode proteger o mecanismo Rhino).

Twittopher
fonte
8
Qual é o benefício de tentar inserir essas regras de negócios em um banco de dados, em vez de colocar a lógica no aplicativo compilado?
Winston Ewert 26/03
6
A externalização das regras @WinstonEWert elimina a necessidade de recompilar e redistribuir o aplicativo caso uma regra seja alterada, adicionada ou removida.
Twittopher 26/03
2
Pergunta interessante! Gostaria de ver uma resposta que não se concentre tanto na segurança, mas nos aspectos de manutenção, confiabilidade e facilidade de uso dessa solução.
Oliver
6
Isso soa semelhante às regras de email do Outlook, que são essencialmente um mecanismo de regras configurável pelo usuário.

Respostas:

37

A inserção de dados dinâmicos em um intérprete da sua linguagem de implementação geralmente é uma má ideia, pois aumenta o potencial de corrupção de dados e a aquisição de aplicativos mal-intencionados. Em outras palavras, você está se esforçando para criar uma vulnerabilidade de injeção de código .

Seu problema pode ser melhor resolvido por um mecanismo de regras ou talvez por uma linguagem específica de domínio (DSL) . Olhe esses conceitos, não há necessidade de reinventar a roda.

Kilian Foth
fonte
16
Mas o JavaScript não seria usado como uma linguagem de script semelhante a DSL aqui? Configuramos os dados necessários (somente leitura), agrupamos o snippet em uma função e os avaliamos com segurança. Como o código não pode fazer nada, exceto retornar um booleano, não haveria oportunidades maliciosas aqui.
amon
6
@Twittopher Arrastar um mecanismo JavaScript inteiro para avaliar alguns predicados ainda parece 1) exagero total, 2) arriscado e 3) propenso a erros para mim. Caso em questão, você usou em ==vez de ===no seu exemplo. Deseja realmente garantir a integridade quando todas as regras sempre devem terminar? Em vez de pular os bastidores para garantir que todas as interações entre Java e JavaScript sejam kosher, por que você não escreve apenas um analisador e interpretador simples, como sugeriu Kilian? Será muito mais fácil adaptar-se às suas necessidades e proteger. Use ANTLR ou algo assim.
Doval 26/03
6
@Doval Escrever um DSL pequeno não é exatamente ciência de foguetes, e eu poderia criar uma linguagem simples em 3 horas a 5 dias. Mas isso parece 1) exagero total, 2) arriscado e 3) propenso a erros para mim. Você realmente quer escrever uma mini-linguagem inteira? E se algumas regras de negócios forem mais complicadas do que o esperado e precisarem de um idioma com todos os recursos? Você está sofrendo da síndrome do não inventado aqui? Em vez de reinventar a roda, por que você não usa um idioma existente? É muito mais fácil usar um idioma que já foi testado em batalha.
amon
14
@amon Quando isso acontece, você encontra um mecanismo de regras real (que não é o JavaScript) como o Killian disse. Equacionar os riscos de ambas as abordagens é enganoso. É necessária apenas uma omissão para destruir todos os seus esforços de garantir um intérprete para um idioma completo; é um processo subtrativo. É muito mais difícil acidentalmente tornar um pequeno DSL perigoso; é um processo aditivo. O tipo de erro que você provavelmente cometerá é interpretar a árvore de sintaxe incorretamente e isso pode ser testado em unidade. Você provavelmente não concederá acidentalmente ao intérprete a capacidade de formatar um disco rígido.
Doval 26/03
4
@amon: Mesmo que os js trecho pode não ter efeitos colaterais, pode optar por não retornar um valor booleano:while (true) ;
Bergi
44

Fiz isso e recomendo que não.

O que fiz foi escrever toda a lógica comercial em Lua e armazenar esse script Lua em um banco de dados. Quando meu aplicativo era iniciado, ele carregava e executava o script. Dessa forma, eu poderia atualizar a lógica comercial do meu aplicativo sem distribuir um novo binário.

Invariavelmente, descobri que sempre precisava atualizar o binário ao fazer alterações. Algumas mudanças estavam no script Lua, mas eu sempre tinha uma lista de mudanças que precisavam ser feitas e, portanto, quase sempre acabava tendo que fazer algumas alterações no binário e algumas no script Lua. Minha imaginação de que eu poderia evitar distribuir binários o tempo todo simplesmente não deu certo.

O que achei muito mais útil foi facilitar a distribuição dos binários. Meu aplicativo verifica automaticamente se há atualizações na inicialização, baixa e instala qualquer atualização. Meus usuários estão, portanto, sempre nos binários mais recentes que enviei. Quase não há diferença entre uma mudança no binário e uma mudança nos scripts. Se eu fizesse de novo, esforçaria ainda mais para tornar a atualização perfeita.

Winston Ewert
fonte
3

Eu não gostaria que o banco de dados contivesse código. Mas você pode fazer algo semelhante fazendo com que o banco de dados contenha nomes de funções e use reflexão para chamá-los. Ao adicionar uma nova condição, você deve adicioná-la ao seu código e ao seu banco de dados, mas pode combinar condições e parâmetros que são passados ​​a eles para criar avaliações bastante complexas.

Em outras palavras, se você tiver numerado departamentos, seria fácil fazer uma verificação UserDepartmentIs e TodayIsAfter e depois combiná-los para ter um Departamento = 2 e Hoje> 15/03/2016. Se você deseja fazer uma verificação TodayIsBefore para poder finalizar a permissão da data, você deve escrever a função TodayIsBefore.

Eu não fiz isso para permissões de usuário, mas fiz para validação de dados, mas deve funcionar.

jmoreno
fonte
2

XACML é a solução que você está realmente procurando. É um tipo de mecanismo de regras focado apenas no controle de acesso. XACML, um padrão definido pelo OASIS, define três partes:

  • uma arquitetura
  • uma linguagem política (que é realmente o que você deseja)
  • um esquema de solicitação / resposta (como você solicita uma decisão de autorização).

A arquitetura é a seguinte:

  • o Policy Decision Point (PDP) é a parte principal da arquitetura. É o componente que avalia solicitações de autorização de entrada em relação a um conjunto conhecido de políticas
  • o Policy Enforcement Point (PEP) é o código que protege seu aplicativo / API / serviço. O PEP intercepta a solicitação de negócios, cria uma solicitação de autorização XACML, a envia ao PDP, recebe uma resposta de volta e aplica a decisão dentro da resposta.
  • o Policy Information Point (PIP) é o componente que pode conectar o PDP a fontes externas de dados, por exemplo, um LDAP, um banco de dados ou um serviço da web. O PIP é útil quando o PEP envia uma solicitação, por exemplo, "Alice pode visualizar o documento nº 12?" e o PDP possui uma política que exige a idade do usuário. O PDP solicitará ao PIP "me dê a idade de Alice" e poderá processar as políticas.
  • o Policy Administration Point (PAP) é o local em que você gerencia toda a solução XACML (definindo atributos, escrevendo políticas e configurando o PDP).

Linguagem de marcação de controle de acesso eXtensible - Arquitetura XACML

Aqui está a aparência do seu primeiro caso de uso:

/*
 * All users in department 1 with over 5 years of seniority can VIEW resource FOOBAR-1, else not authorized
 * 
 */
 policy departmentOne{
    target clause department == 1
    apply firstApplicable
    /**
     * All users in department 1 with over 5 years of seniority can VIEW resource FOOBAR-1, else not authorized
     */
    rule allowFooBar1{
        target clause resourceId=="FOOBAR-1" and seniority>=5 and actionId=="VIEW"
        permit
    }
    rule denyOtherAccess{
        deny
    }

 }

Seu segundo caso de uso seria:

 /*
  * "All users in department 2, if the date is after 03/15/2016, can VIEW resource FOOBAR-2, else not authorized"
  *  
  */
  policy departmentTwo{
    target clause department == 1
    apply firstApplicable
    rule allowFooBar2{
        target clause resourceId=="FOOBAR-1" and seniority>=5 and currentDate>"2016/03/15":date and actionId=="VIEW"
        permit
    }
    rule denyOtherAccess{
        deny
    }
  }

Você pode combinar os dois casos de uso em uma única política usando referências:

  policyset global{
    apply firstApplicable
    departmentOne
    departmentTwo
  }

E pronto!

Você pode ler mais sobre XACML e ALFA em:

David Brossard
fonte
0

O que você realmente deseja aqui é XACML . Ele praticamente fornece exatamente o que você deseja. Você não precisa necessariamente implementar a arquitetura completa com todas as funções sendo completamente separadas ... se você tiver apenas um único aplicativo, provavelmente poderá integrar o PDP e o PEP em seu aplicativo com balana e o PIP é o que for seu banco de dados de usuário existente é.

Agora, em qualquer lugar do seu aplicativo que você precise autorizar algo, crie uma solicitação XACML com o usuário, a ação e o contexto, e o mecanismo XACML tomará a decisão com base nos arquivos de política XACML que você escreveu. Esses arquivos de políticas podem ser mantidos no banco de dados ou no sistema de arquivos ou onde quer que você queira manter a configuração. O Axiomatics tem uma boa alternativa à representação XML XACML chamada ALFA, que é um pouco mais fácil de ler do que o XML bruto, e um plug-in do Eclipse para gerar XML XACML a partir das políticas do ALFA.

gregsymons
fonte
11
Como isso responde à pergunta?
Gnat
Ele está especificamente tentando implementar um sistema de autorização configurado externamente. O XACML é um sistema de autorização configurado para uso externo, pronto para uso, que cobre muito bem seu caso de uso específico. Admito que pode não ser uma ótima resposta para a questão mais geral da execução dinâmica de código, mas é uma boa solução para sua pergunta específica.
gregsymons
0

Fizemos isso na minha empresa atual e estamos muito felizes com os resultados.

Nossas expressões são escritas em js e até as usamos para restringir os resultados que os usuários podem obter ao consultar o ElasticSearch.

O truque é garantir que haja informações suficientes disponíveis para tomar a decisão, para que você possa realmente escrever as permissões que desejar, sem alterações no código, mas ao mesmo tempo mantendo-o rápido.

Não estamos realmente preocupados com ataques de injeção de código, pois as permissões são escritas por aqueles que não precisam atacar o sistema. E o mesmo se aplica a ataques do DOS como owhile(true) exemplo. Os administradores do sistema não precisam fazer isso, eles podem apenas remover as permissões de todos ...

Atualizar:

Algo como XACML parece ser melhor como um ponto central de gerenciamento de autenticação para uma organização. Nosso caso de uso é um pouco diferente, pois nossos clientes normalmente não têm um departamento de TI para executar tudo isso. Precisávamos de algo independente, mas tentamos preservar o máximo de flexibilidade possível.

Adagios
fonte