Dynamic Code Evaluation in Java - Clever oder schlampig?

30

Ich versuche, ein flexibles ACL-Framework in Java für meine Anwendung zu erstellen.

Viele ACL-Frameworks basieren auf einer Whitelist von Regeln, wobei eine Regel die Form Eigentümer: Aktion: Ressource hat . Beispielsweise,

  • "JOHN kann die Ressource FOOBAR-1 ANZEIGEN"
  • "MARY kann die Ressource FOOBAR-1 ANZEIGEN"
  • "MARY kann Ressource FOOBAR-1 BEARBEITEN"

Dies ist attraktiv, da die Regeln einfach in einer Datenbank serialisiert / beibehalten werden können. Meine Anwendung verfügt jedoch über eine komplexe Geschäftslogik. Beispielsweise,

  • "Alle Benutzer in Abteilung 1 mit einem Dienstalter von über 5 Jahren können die Ressource FOOBAR-1 ANZEIGEN, die sonst nicht autorisiert ist."
  • "Alle Benutzer in Abteilung 2 können die Ressource FOOBAR-2 anzeigen, wenn das Datum nach dem 15.03.2016 liegt. Andernfalls sind sie nicht autorisiert."

Auf den ersten Blick wäre es ein Alptraum, ein Datenbankschema zu entwickeln, das mit unendlich komplexen Regeln wie diesen umgehen kann. Daher scheint es, als müsste ich sie in die kompilierte Anwendung "backen", sie für jeden Benutzer auswerten und dann als Ergebnis der Auswertung Regeln für Eigentümer: Aktion: Ressource erstellen . Ich möchte vermeiden, die Logik in die kompilierte Anwendung einzubinden.

Daher habe ich mir überlegt, eine Regel in Form eines Prädikats darzustellen : action: resource , wobei das Prädikat ein boolescher Ausdruck ist, der festlegt, ob ein Benutzer zulässig ist. Das Prädikat wäre eine Zeichenfolge eines JavaScript-Ausdrucks, der von Javas Rhino-Engine ausgewertet werden könnte. Beispielsweise,

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

Auf diese Weise können die Prädikate problemlos in der Datenbank gespeichert werden.

Ist das schlau ? Ist das schlampig ? Ist das eine Spielerei ? Ist das überarbeitet ? Ist das sicher (anscheinend kann Java die Rhino Engine sandboxen).

Twittopher
quelle
8
Was ist der Vorteil des Versuchs, diese Geschäftsregeln in eine Datenbank zu verschieben, anstatt die Logik in die kompilierte Anwendung zu übernehmen?
Winston Ewert
6
@WinstonEWert Durch die Externalisierung der Regeln muss die Anwendung nicht erneut kompiliert und verteilt werden, wenn eine Regel geändert, hinzugefügt oder entfernt wird.
Twittopher
2
Interessante Frage! Ich würde gerne eine Antwort erhalten, die sich nicht so sehr auf die Sicherheit konzentriert, sondern vielmehr auf die Aspekte Wartung, Zuverlässigkeit und Benutzerfreundlichkeit einer solchen Lösung.
Oliver
6
Dies klingt ähnlich wie bei Outlook-E-Mail-Regeln, bei denen es sich im Wesentlichen um ein vom Benutzer konfigurierbares Regelmodul handelt.

Antworten:

37

Das Weiterleiten dynamischer Daten in einen Interpreter Ihrer Implementierungssprache ist in der Regel eine schlechte Idee, da hierdurch die Möglichkeit einer Datenbeschädigung in die Möglichkeit einer böswilligen Übernahme von Anwendungen eskaliert. Mit anderen Worten, werden Sie aus dem Weg zu schaffen , eine Code - Injektion Verwundbarkeit.

Ihr Problem kann besser durch eine Regelengine oder möglicherweise eine domänenspezifische Sprache (DSL) gelöst werden . Schauen Sie sich diese Konzepte an, Sie müssen das Rad nicht neu erfinden.

Kilian Foth
quelle
16
Aber würde JavaScript hier nicht als DSL-ähnliche Scrip-Sprache verwendet werden? Wir richten die erforderlichen Daten ein (schreibgeschützt), verpacken das Snippet in eine Funktion und werten es sicher aus. Da der Code nur einen Booleschen Wert zurückgeben kann, gibt es hier keine böswilligen Möglichkeiten.
Amon
6
@Twittopher Das Ziehen einer gesamten JavaScript-Engine, um einige Prädikate auszuwerten, scheint mir immer noch wie 1) völliger Overkill, 2) riskant und 3) fehleranfällig. In diesem Fall haben Sie ==anstelle von ===in Ihrem Beispiel verwendet. Wollen Sie wirklich vollständige Angaben machen, wenn alle Regeln wohl immer enden sollten? Warum schreiben Sie nicht einfach einen einfachen Parser und Interpreter, wie Kilian es vorgeschlagen hat, anstatt durch die Rahmen zu springen, um sicherzustellen, dass alle Interaktionen zwischen Java und JavaScript koscher sind? Es wird viel einfacher sein, auf Ihre Bedürfnisse zuzuschneiden und zu sichern. Benutze ANTLR oder so.
Doval
6
@Doval Eine kleine DSL zu schreiben ist nicht gerade ein Hexenwerk, und ich könnte in 3 Stunden bis 5 Tagen eine einfache Sprache entwickeln. Aber das scheint mir 1) totaler Overkill, 2) riskant und 3) fehleranfällig zu sein. Willst du wirklich eine ganze Minisprache schreiben? Was ist, wenn einige Geschäftsregeln komplizierter als erwartet sind und eine vollständige Sprache benötigen? Leiden Sie unter dem Not Invented Here-Syndrom? Warum verwenden Sie keine vorhandene Sprache, anstatt das Rad neu zu erfinden? Es ist viel einfacher, eine Sprache zu verwenden, die bereits kampferprobt wurde.
Amon
14
@amon Wenn das passiert, findest du eine echte Regel-Engine (die JavaScript nicht unterstützt), wie Killian sagte. Die Risiken beider Ansätze gleichzusetzen, ist irreführend. Es ist nur eine Auslassung erforderlich, um all Ihre Bemühungen zu zerstören, einen Dolmetscher für eine Sprache zu finden, die vollständig ist. Es ist ein subtraktiver Prozess. Es ist viel schwieriger, aus Versehen eine kleine DSL gefährlich zu machen. Es ist ein additiver Prozess. Die Art von Fehler, den Sie wahrscheinlich machen werden, besteht darin, den Syntaxbaum falsch zu interpretieren, und das kann Unit-getestet werden. Sie werden dem Interpreter wahrscheinlich nicht versehentlich die Möglichkeit geben, eine Festplatte zu formatieren.
Doval
4
@amon: Auch wenn das js-Snippet möglicherweise keine Nebenwirkungen hat, kann es sich dafür entscheiden, keinen booleschen Wert zurückzugeben:while (true) ;
Bergi
44

Ich habe das getan, und ich empfehle Ihnen, das nicht zu tun.

Ich habe die gesamte Geschäftslogik in Lua geschrieben und dieses Lua-Skript in einer Datenbank gespeichert. Beim Start meiner Anwendung wurde das Skript geladen und ausgeführt. Auf diese Weise konnte ich die Geschäftslogik meiner Anwendung aktualisieren, ohne eine neue Binärdatei zu verteilen.

Ich habe immer festgestellt, dass ich die Binärdatei immer aktualisieren muss, wenn ich Änderungen vornehme. Einige Änderungen wurden im Lua-Skript vorgenommen, aber ich hatte immer eine Liste der Änderungen, die vorgenommen werden mussten, und so musste ich fast immer einige Änderungen in der Binärdatei und einige Änderungen im Lua-Skript vornehmen. Meine Vorstellung, dass ich es vermeiden könnte, ständig Binärdateien zu verteilen, kam einfach nicht auf.

Ich fand es viel hilfreicher, die Verteilung von Binärdateien zu vereinfachen. Meine Anwendung sucht beim Start automatisch nach Updates, lädt diese herunter und installiert sie. Meine Benutzer sind also immer auf den neuesten Binärdateien, die ich gepusht habe. Es gibt fast keinen Unterschied zwischen einer Änderung in der Binärdatei und einer Änderung in den Skripten. Wenn ich es noch einmal machen würde, würde ich mich noch mehr bemühen, das Update nahtlos zu machen.

Winston Ewert
quelle
3

Ich hätte nicht die Datenbank Code enthalten. Sie können jedoch etwas Ähnliches tun, indem Sie die Datenbank Funktionsnamen enthalten lassen und diese dann mithilfe von Reflection aufrufen. Wenn Sie eine neue Bedingung hinzufügen, müssen Sie sie zu Ihrem Code und Ihrer Datenbank hinzufügen. Sie können jedoch Bedingungen und Parameter kombinieren, die an sie übergeben werden, um recht komplexe Auswertungen zu erstellen.

Mit anderen Worten, wenn Sie über nummerierte Abteilungen verfügen, ist es einfach, eine UserDepartmentIs-Prüfung und eine TodayIsAfter-Prüfung durchzuführen und diese dann zu einer Abteilung = 2 und Today> 15.03.2016 zu kombinieren. Wenn Sie dann eine TodayIsBefore-Überprüfung durchführen möchten, damit Sie das Datum der Berechtigung beenden können, müssen Sie die TodayIsBefore-Funktion schreiben.

Ich habe dies nicht für Benutzerberechtigungen getan, sondern für die Datenüberprüfung, aber es sollte funktionieren.

jmoreno
quelle
2

XACML ist die Lösung, nach der Sie wirklich suchen. Es handelt sich um eine Art Regelmodul, das sich nur auf die Zugriffssteuerung konzentriert. XACML, ein von OASIS definierter Standard, besteht aus drei Teilen:

  • eine architektur
  • eine politische Sprache (was wirklich das ist, was Sie wollen)
  • ein Anforderungs- / Antwortschema (wie Sie nach einer Autorisierungsentscheidung fragen).

Die Architektur ist wie folgt:

  • Der Policy Decision Point (PDP) ist der Kern der Architektur. Es ist die Komponente, die eingehende Autorisierungsanforderungen anhand bekannter Richtlinien bewertet
  • Der Policy Enforcement Point (PEP) ist der Code, der Ihre Anwendung / API / Ihren Dienst schützt. Der PEP fängt die Geschäftsanforderung ab, erstellt eine XACML-Autorisierungsanforderung, sendet sie an den PDP, empfängt eine Antwort zurück und erzwingt die Entscheidung innerhalb der Antwort.
  • Der Policy Information Point (PIP) ist die Komponente, die den PDP mit externen Datenquellen verbinden kann, z. B. einem LDAP, einer Datenbank oder einem Webdienst. Der PIP ist praktisch, wenn der PEP eine Anfrage sendet, z. B. "Kann Alice Dokument # 12 anzeigen?". und der PDP hat eine Richtlinie, die das Alter des Benutzers erfordert. Der PDP fragt den PIP nach "Gib mir Alices Alter" und kann dann die Richtlinien verarbeiten.
  • Der Policy Administration Point (PAP) ist der Ort, an dem Sie die gesamte XACML-Lösung verwalten (Definieren von Attributen, Schreiben von Richtlinien und Konfigurieren des PDP).

eXtensible Access Control Markup Language - XACML-Architektur

So sieht Ihr erster Anwendungsfall aus:

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

 }

Ihr zweiter Anwendungsfall wäre:

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

Sie können beide Anwendungsfälle in einer einzigen Richtlinie kombinieren, indem Sie Verweise verwenden:

  policyset global{
    apply firstApplicable
    departmentOne
    departmentTwo
  }

Und du bist fertig!

Weitere Informationen zu XACML und ALFA finden Sie unter:

David Brossard
quelle
0

Was Sie hier wirklich wollen, ist XACML . Es gibt Ihnen so ziemlich genau das, was Sie wollen. Sie müssen nicht unbedingt die gesamte Architektur implementieren, da alle Rollen vollständig voneinander getrennt sind. Wenn Sie nur eine einzige Anwendung haben, können Sie wahrscheinlich mit balana die Integration von PDP und PEP in Ihre App vermeiden Ihre vorhandene Benutzerdatenbank ist.

Jetzt erstellen Sie überall in Ihrer App, wo Sie etwas autorisieren müssen, eine XACML-Anforderung, die den Benutzer, die Aktion und den Kontext enthält, und die XACML-Engine trifft die Entscheidung auf der Grundlage der von Ihnen geschriebenen XACML-Richtliniendateien. Diese Richtliniendateien können in der Datenbank, im Dateisystem oder an einem beliebigen Ort gespeichert werden, an dem die Konfiguration gespeichert werden soll. Axiomatics bietet eine nette Alternative zur XACML-XML-Darstellung namens ALFA, die etwas einfacher zu lesen ist als die unformatierte XML-Darstellung, und ein Eclipse-Plugin zum Generieren von XACML-XML aus ALFA-Richtlinien.

gregsymons
quelle
1
Wie beantwortet dies die gestellte Frage?
Mücke
Er versucht speziell, ein extern konfiguriertes Berechtigungssystem zu implementieren. XACML ist ein sofort einsatzbereites, extern konfiguriertes Autorisierungssystem, das seinen spezifischen Anwendungsfall sehr gut abdeckt. Ich gebe zu, dass es möglicherweise keine gute Antwort auf die allgemeinere Frage der dynamischen Codeausführung ist, aber es ist eine gute Lösung für seine spezifische Frage.
Gregsymons
0

Das haben wir in meiner jetzigen Firma gemacht und wir sind sehr zufrieden mit den Ergebnissen.

Unsere Ausdrücke sind in js geschrieben und wir verwenden sie sogar, um die Ergebnisse einzuschränken, die Benutzer durch die Abfrage von ElasticSearch erhalten können.

Der Trick besteht darin, sicherzustellen, dass genügend Informationen zur Verfügung stehen, um die Entscheidung zu treffen, sodass Sie wirklich schreiben können, was immer Sie möchten, ohne Codeänderungen, aber gleichzeitig schnell.

Wir sind nicht wirklich besorgt über Code-Injection-Angriffe, da die Berechtigungen von denen geschrieben werden, die das System nicht angreifen müssen. Gleiches gilt für DOS-Attacken wie diewhile(true) Beispiel. Die Administratoren des Systems müssen das nicht tun, sie könnten einfach alle Berechtigungen entfernen ...

Aktualisieren:

So etwas wie XACML scheint als zentraler Authentifizierungsverwaltungspunkt für eine Organisation besser zu sein. Unser Anwendungsfall unterscheidet sich insofern geringfügig, als unsere Kunden typischerweise keine IT-Abteilung haben, um all dies auszuführen. Wir brauchten etwas Eigenständiges, versuchten aber, so viel Flexibilität wie möglich zu bewahren.

Adagios
quelle