Évaluation de code dynamique en Java - Clever or Sloppy?

30

J'essaie de créer un cadre ACL flexible en Java pour mon application.

De nombreux frameworks ACL sont construits sur une liste blanche de règles, où une règle est sous la forme propriétaire: action: ressource . Par exemple,

  • "JOHN peut VOIR la ressource FOOBAR-1"
  • "MARIE peut VOIR la ressource FOOBAR-1"
  • "MARIE peut MODIFIER la ressource FOOBAR-1"

Ceci est intéressant car les règles peuvent facilement être sérialisées / conservées dans une base de données. Mais mon application a une logique métier complexe. Par exemple,

  • "Tous les utilisateurs du département 1 avec plus de 5 ans d'ancienneté peuvent VOIR la ressource FOOBAR-1, sinon non autorisé"
  • "Tous les utilisateurs du département 2, si la date est postérieure au 15/03/2016, peuvent VOIR la ressource FOOBAR-2, sinon non autorisé"

À première vue, ce serait un cauchemar de concevoir un schéma de base de données capable de gérer des règles infiniment complexes comme celles-ci. Par conséquent, il semble que j'aurais besoin de les "cuire" dans l'application compilée, de les évaluer pour chaque utilisateur, puis de produire des règles owner: action: resource à la suite de l'évaluation. Je veux éviter de faire cuire la logique dans l'application compilée.

Donc, je pensais représenter une règle sous forme de prédicat : action: ressource , où le prédicat est une expression booléenne qui détermine si un utilisateur est autorisé. Le prédicat serait une chaîne d'une expression JavaScript qui pourrait être évaluée par le moteur Rhino de Java. Par exemple,

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

Ce faisant, les prédicats pourraient facilement être conservés dans la base de données.

Est-ce intelligent ? Est-ce bâclé ? Est-ce gimmicky ? Est-ce trop conçu ? Est-ce sûr (apparemment, Java peut sandboxer le moteur Rhino).

Twittopher
la source
8
Quel est l'avantage d'essayer de pousser ces règles métier dans une base de données plutôt que de mettre la logique dans l'application compilée?
Winston Ewert
6
@WinstonEWert L'externalisation des règles élimine la nécessité de recompiler et de redistribuer l'application en cas de modification, d'ajout ou de suppression d'une règle.
Twittopher
2
@WinstonEwert: Y a
2
Question interessante! J'aimerais voir une réponse qui ne se concentre pas tant sur la sécurité mais plutôt sur les aspects de maintenance, de fiabilité et de facilité d'utilisation d'une telle solution.
olivier
6
Cela ressemble aux règles de messagerie Outlook qui sont essentiellement un moteur de règles configurable par l'utilisateur.

Réponses:

37

Le transfert de données dynamiques vers un interpréteur de votre langage d'implémentation est généralement une mauvaise idée, car il fait passer le risque de corruption de données à un potentiel de prise de contrôle d'applications malveillantes. En d'autres termes, vous vous efforcez de créer une vulnérabilité d' injection de code .

Votre problème peut être mieux résolu par un moteur de règles ou peut-être un langage spécifique au domaine (DSL) . Recherchez ces concepts, il n'est pas nécessaire de réinventer la roue.

Kilian Foth
la source
16
Mais JavaScript ne serait-il pas utilisé ici comme langage de script de type DSL? Nous configurons les données nécessaires (en lecture seule), encapsulons l'extrait de code dans une fonction et l'évaluons en toute sécurité. Étant donné que le code ne peut rien faire sauf renvoyer un booléen, il n'y aurait aucune opportunité malveillante ici.
amon
6
@Twittopher Faire glisser un moteur JavaScript entier pour évaluer certains prédicats me semble toujours 1) une exagération totale, 2) risqué et 3) propice aux erreurs. Par exemple, vous avez utilisé ==au lieu de ===dans votre exemple. Voulez-vous vraiment fournir une complétude complète lorsque toutes les règles devraient sans doute toujours se terminer? Au lieu de sauter à travers des cerceaux pour vous assurer que toutes les interactions entre Java et JavaScript sont casher, pourquoi n'écrivez-vous pas simplement un simple analyseur et interprète comme Kilian l'a suggéré? Il sera beaucoup plus facile de s'adapter à vos besoins et de le sécuriser. Utilisez ANTLR ou quelque chose.
Doval
6
@Doval Écrire une petite DSL n'est pas exactement une science complexe, et je pourrais concocter un langage simple en 3 heures à 5 jours. Mais cela semble être 1) une exagération totale, 2) risqué et 3) sujet à erreur pour moi. Voulez-vous vraiment écrire une mini-langue entière? Que se passe-t-il si certaines règles métier sont plus compliquées que prévu et nécessitent un langage complet? Souffrez-vous du syndrome Not Invented Here? Au lieu de réinventer la roue, pourquoi n'utilisez-vous pas une langue existante? Il est beaucoup plus facile d'utiliser une langue qui a déjà été testée au combat.
amon
14
@amon Lorsque cela se produit, vous trouvez un véritable moteur de règles (ce qui n'est pas JavaScript) comme l'a dit Killian. Assimiler les risques des deux approches est trompeur. Il suffit d'une seule omission pour détruire tous vos efforts pour trouver un interprète pour une langue complète; c'est un processus soustractif. Il est beaucoup plus difficile de rendre accidentellement un petit DSL dangereux; c'est un processus additif. Le type d'erreur que vous risquez de faire est d'interpréter incorrectement l'arbre de syntaxe, et cela peut être testé unitaire. Vous n'allez probablement pas accidentellement donner à l'interprète le formatage d'un disque dur.
Doval
4
@amon: Même si l'extrait js peut ne pas avoir d'effets secondaires, il peut choisir de ne pas renvoyer de valeur booléenne:while (true) ;
Bergi
44

Je l'ai fait et je vous recommande de ne pas le faire.

Ce que j'ai fait, c'est d'écrire toute la logique métier dans Lua et de stocker ce script Lua dans une base de données. Au démarrage de mon application, elle chargeait et exécutait le script. De cette façon, je pouvais mettre à jour la logique métier de mon application sans distribuer un nouveau binaire.

J'ai toujours constaté que j'avais toujours besoin de mettre à jour le binaire lors des modifications. Certaines modifications étaient dans le script Lua, mais j'avais invariablement une liste des modifications qui devaient être apportées, et donc je finissais presque toujours par devoir apporter des modifications au binaire et des modifications au script Lua. Mon imagination que je pouvais éviter de distribuer des binaires tout le temps ne s'est tout simplement pas concrétisée.

Ce que j'ai trouvé beaucoup plus utile était de faciliter la distribution des binaires. Mon application recherche automatiquement les mises à jour au démarrage, télécharge et installe toute mise à jour. Mes utilisateurs sont donc toujours sur les derniers binaires que j'ai poussés. Il n'y a presque aucune différence entre un changement dans le binaire et un changement dans les scripts. Si je recommençais, je ferais encore plus d'efforts pour rendre la mise à jour transparente.

Winston Ewert
la source
3

Je ne voudrais pas que la base de données contienne du code. Mais vous pouvez faire quelque chose de similaire en faisant en sorte que la base de données contienne des noms de fonction, puis en utilisant la réflexion pour les appeler. Lorsque vous ajoutez une nouvelle condition, vous devez l'ajouter à votre code et à votre base de données, mais vous pouvez combiner des conditions et des paramètres qui leur sont transmis pour créer des évaluations assez complexes.

En d'autres termes, si vous avez des départements numérotés, il serait facile d'avoir un contrôle UserDepartmentIs et un contrôle TodayIsAfter puis de les combiner pour avoir un Department = 2 et Today> 03/15/2016. Si vous souhaitez ensuite avoir une vérification TodayIsBefore afin de pouvoir terminer la date de l'autorisation, vous devez écrire la fonction TodayIsBefore.

Je ne l'ai pas fait pour les autorisations utilisateur, mais je l'ai fait pour la validation des données, mais cela devrait fonctionner.

jmoreno
la source
2

XACML est la solution que vous recherchez vraiment. Il s'agit d'un type de moteur de règles qui se concentre uniquement sur le contrôle d'accès. XACML, une norme définie par OASIS, définit trois parties:

  • une architecture
  • un langage de politique (qui est vraiment ce que vous voulez)
  • un schéma de demande / réponse (comment demander une décision d'autorisation).

L'architecture est la suivante:

  • le point de décision politique (PDP) est l'élément central de l'architecture. C'est le composant qui évalue les demandes d'autorisation entrantes par rapport à un ensemble connu de politiques
  • le Policy Enforcement Point (PEP) est le morceau de code qui protège votre application / API / service. Le PEP intercepte la demande métier, crée une demande d'autorisation XACML, l'envoie au PDP, reçoit une réponse et applique la décision dans la réponse.
  • le Policy Information Point (PIP) est le composant qui peut connecter le PDP à des sources de données externes, par exemple un LDAP, une base de données ou un service Web. Le PIP est utile lorsque le PEP envoie une demande, par exemple "Alice peut-elle voir le document n ° 12?" et le PDP a une politique qui requiert l'âge de l'utilisateur. Le PDP demandera au PIP "Donnez-moi l'âge d'Alice" et pourra ensuite traiter les politiques.
  • le point d'administration des politiques (PAP) est l'endroit où vous gérez l'ensemble de la solution XACML (définition des attributs, écriture des politiques et configuration du PDP).

Langage de balisage de contrôle d'accès eXtensible - Architecture XACML

Voici à quoi ressemble votre premier cas d'utilisation:

/*
 * 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
    }

 }

Votre deuxième cas d'utilisation serait:

 /*
  * "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
    }
  }

Vous pouvez combiner les deux cas d'utilisation en une seule stratégie à l'aide de références:

  policyset global{
    apply firstApplicable
    departmentOne
    departmentTwo
  }

Et tu as fini!

Vous pouvez en savoir plus sur XACML et ALFA à partir de:

David Brossard
la source
0

Ce que vous voulez vraiment ici, c'est XACML . Cela vous donne à peu près exactement ce que vous voulez. Vous n'avez pas nécessairement à implémenter l'architecture complète avec tous les rôles complètement séparés ... si vous n'avez qu'une seule application, vous pouvez probablement vous en sortir en intégrant le PDP et le PEP dans votre application avec balana et le PIP est tout votre base de données utilisateur existante est.

Maintenant, n'importe où dans votre application, vous devez autoriser quelque chose, vous créez une demande XACML qui a l'utilisateur, l'action et le contexte, et le moteur XACML prendra la décision en fonction des fichiers de stratégie XACML que vous avez écrits. Ces fichiers de stratégie peuvent être conservés dans la base de données ou sur le système de fichiers, ou là où vous souhaitez conserver la configuration. Axiomatics a une belle alternative à la représentation XML XACML appelée ALFA qui est un peu plus facile à lire que le XML brut, et un plugin Eclipse pour générer du XML XACML à partir des politiques ALFA.

gregsymons
la source
1
comment cela répond-il à la question posée?
moucher
Il essaie spécifiquement d'implémenter un système d'autorisation configuré en externe. XACML est un système d'autorisation configuré en externe prêt à l'emploi qui couvre très bien son cas d'utilisation spécifique. J'admets que ce n'est peut-être pas une excellente réponse à la question plus générale de l'exécution de code dynamique, mais c'est une bonne solution à sa question spécifique.
gregsymons
0

Nous l'avons fait dans mon entreprise actuelle et nous sommes très satisfaits des résultats.

Nos expressions sont écrites en js, et nous les utilisons même pour restreindre les résultats que les utilisateurs peuvent obtenir en interrogeant ElasticSearch.

L'astuce consiste à s'assurer que suffisamment d'informations sont disponibles pour prendre la décision, afin que vous puissiez vraiment écrire ce que vous voulez sans changer de code, mais en même temps en le gardant rapide.

Nous ne sommes pas vraiment inquiets des attaques par injection de code, car les autorisations sont écrites par ceux qui n'ont pas besoin d'attaquer le système. Et la même chose s'applique aux attaques DOS comme dans l' while(true)exemple. Les administrateurs du système n'ont pas besoin de le faire, ils peuvent simplement supprimer les autorisations de tout le monde ...

Mise à jour:

Quelque chose comme XACML semble être meilleur en tant que point central de gestion d'authentification pour une organisation. Notre cas d'utilisation est légèrement différent en ce que nos clients n'ont généralement pas de service informatique pour gérer tout cela. Nous avions besoin de quelque chose de autonome, mais nous avons essayé de conserver autant de flexibilité que possible.

Adagios
la source