Evaluación dinámica de código en Java: ¿inteligente o descuidado?

30

Estoy tratando de crear un marco ACL flexible en Java para mi aplicación.

Muchos marcos de ACL se basan en una lista blanca de reglas, donde una regla tiene la forma de propietario: acción: recurso . Por ejemplo,

  • "JOHN puede VER el recurso FOOBAR-1"
  • "MARY puede VER el recurso FOOBAR-1"
  • "MARY puede EDITAR el recurso FOOBAR-1"

Esto es atractivo porque las reglas se pueden serializar / persistir fácilmente en una base de datos. Pero mi aplicación tiene una lógica empresarial compleja. Por ejemplo,

  • "Todos los usuarios en el departamento 1 con más de 5 años de antigüedad pueden VER el recurso FOOBAR-1, de lo contrario no están autorizados"
  • "Todos los usuarios del departamento 2, si la fecha es posterior al 15/03/2016, pueden VER el recurso FOOBAR-2, de lo contrario no están autorizados"

Pensándolo bien, sería una pesadilla diseñar un esquema de base de datos que pudiera manejar reglas infinitamente complejas como estas. Por lo tanto, parece que tendría que "hornearlos" en la aplicación compilada, evaluarlos para cada usuario y luego producir propietario: acción: reglas de recursos como resultado de la evaluación. Quiero evitar incluir la lógica en la aplicación compilada.

Entonces, estaba pensando en representar una regla en forma de predicado : acción: recurso , donde el predicado es una expresión booleana que determina si un usuario está permitido. El predicado sería una cadena de una expresión de JavaScript que podría ser evaluada por el motor Rhino de Java. Por ejemplo,

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

Al hacerlo, los predicados podrían fácilmente persistir en la base de datos.

¿Es esto inteligente ? ¿Es esto descuidado ? ¿Esto es un truco ? ¿Está sobre-diseñado ? ¿Es esto seguro (aparentemente, Java puede proteger el motor Rhino).

Twittopher
fuente
8
¿Cuál es el beneficio de intentar insertar estas reglas de negocio en una base de datos en lugar de poner la lógica en la aplicación compilada?
Winston Ewert
66
@WinstonEWert La externalización de las reglas elimina la necesidad de volver a compilar y redistribuir la aplicación en caso de que se cambie, agregue o elimine una regla.
Twittopher
2
¡Interesante pregunta! Me gustaría ver una respuesta que no se centre tanto en la seguridad, sino en los aspectos de mantenimiento, confiabilidad y facilidad de uso de dicha solución.
Oliver
66
Esto suena similar a las reglas de correo electrónico de Outlook, que es esencialmente un motor de reglas configurable por el usuario.

Respuestas:

37

Conectar datos dinámicos a un intérprete de su lenguaje de implementación suele ser una mala idea, ya que aumenta el potencial de corrupción de datos a un potencial de adquisición de aplicaciones maliciosas. En otras palabras, está haciendo todo lo posible para crear una vulnerabilidad de inyección de código .

Su problema puede resolverse mejor mediante un motor de reglas o tal vez un lenguaje de dominio específico (DSL) . Mire esos conceptos, no hay necesidad de reinventar la rueda.

Kilian Foth
fuente
16
¿Pero no se usaría JavaScript como un lenguaje de script similar a DSL aquí? Configuramos los datos necesarios (solo lectura), ajustamos el fragmento en una función y lo evaluamos de forma segura. Como el código no puede hacer nada excepto devolver un valor booleano, aquí no habría oportunidades maliciosas.
amon
66
@Twittopher Arrastrar un motor de JavaScript completo para evaluar algunos predicados todavía parece 1) exageración total, 2) arriesgado y 3) propenso a errores. Caso en cuestión, usaste en ==lugar de ===en tu ejemplo. ¿Realmente desea proporcionar la integridad de turing cuando todas las reglas deberían terminar definitivamente? En lugar de saltar a través de los aros para asegurarte de que todas las interacciones entre Java y JavaScript sean kosher, ¿por qué no escribes un simple analizador e intérprete como sugirió Kilian? Será mucho más fácil adaptarlo a sus necesidades y asegurarlo. Usa ANTLR o algo así.
Doval
66
@Doval Escribir un DSL pequeño no es exactamente ciencia espacial, y podría preparar un lenguaje simple en 3 horas a 5 días. Pero esto parece 1) exageración total, 2) arriesgado y 3) propenso a errores. ¿De verdad quieres escribir un mini idioma completo? ¿Qué sucede si algunas reglas comerciales son más complicadas de lo esperado y necesitan un lenguaje con todas las funciones? ¿Sufre usted del síndrome de no inventado aquí? En lugar de reinventar la rueda, ¿por qué no usas un lenguaje existente? Es mucho más fácil usar un lenguaje que ya ha sido probado en batalla.
amon
14
@amon Cuando eso sucede, encuentras un motor de reglas real (que JavaScript no es) como dijo Killian. Equiparar los riesgos de ambos enfoques es engañoso. Solo se necesita una omisión para destruir todos sus esfuerzos de obtener un intérprete para un lenguaje completo; Es un proceso sustractivo. Es mucho más difícil hacer accidentalmente que un DSL pequeño sea peligroso; Es un proceso aditivo. El tipo de error que es probable que cometas es interpretar el árbol de sintaxis incorrectamente, y eso se puede probar de forma unitaria. Probablemente no va a darle accidentalmente al intérprete la capacidad de formatear un disco duro.
Doval
44
@amon: Incluso si el fragmento js podría no tener efectos secundarios, podría optar por no devolver un valor booleano:while (true) ;
Bergi
44

Hice esto y te recomiendo que no lo hagas.

Lo que hice fue escribir toda la lógica de negocios en Lua, y almacené ese script Lua en una base de datos. Cuando mi aplicación se inicia, se carga y ejecuta el script. De esa manera podría actualizar la lógica de negocios de mi aplicación sin distribuir un nuevo binario.

Invariablemente descubrí que siempre necesitaba actualizar el binario al hacer cambios. Algunos cambios estaban en el script de Lua, pero invariablemente tenía una lista de cambios que debían hacerse, por lo que casi siempre terminaba teniendo que hacer algunos cambios en el binario y algunos cambios en el script de Lua. Mi imaginación de que podría evitar distribuir binarios todo el tiempo simplemente no funcionó.

Lo que encontré mucho más útil fue facilitar la distribución de binarios. Mi aplicación busca automáticamente actualizaciones al inicio, descargas e instala cualquier actualización. Por lo tanto, mis usuarios siempre están en los últimos binarios que he introducido. Casi no hay diferencia entre un cambio en el binario y un cambio en los scripts. Si lo volviera a hacer, me esforzaría aún más para que la actualización sea perfecta.

Winston Ewert
fuente
3

No quisiera que la base de datos contenga código. Pero puede hacer algo similar haciendo que la base de datos contenga nombres de funciones y luego usando la reflexión para llamarlos. Cuando agrega una nueva condición, debe agregarla a su código y a su base de datos, pero puede combinar condiciones y parámetros que se les pasan para crear evaluaciones bastante complejas.

En otras palabras, si tiene departamentos numerados, sería fácil tener una verificación UserDepartmentIs y una verificación TodayIsAfter y luego combinarlas para tener un Departamento = 2 y Today> 15/03/2016. Si luego desea tener una comprobación TodayIsBefore para poder finalizar la fecha del permiso, debe escribir la función TodayIsBefore.

No he hecho esto para los permisos de usuario, pero lo he hecho para la validación de datos, pero debería funcionar.

jmoreno
fuente
2

XACML es la solución que realmente estás buscando. Es un tipo de motor de reglas que se centra únicamente en el control de acceso. XACML, un estándar definido por OASIS, define tres partes:

  • una arquitectura
  • un lenguaje de políticas (que es realmente lo que quieres)
  • un esquema de solicitud / respuesta (cómo solicita una decisión de autorización).

La arquitectura es la siguiente:

  • El Policy Decision Point (PDP) es la parte central de la arquitectura. Es el componente que evalúa las solicitudes de autorización entrantes contra un conjunto conocido de políticas.
  • El Punto de cumplimiento de políticas (PEP) es el código que protege su aplicación / API / servicio. El PEP intercepta la solicitud comercial, crea una solicitud de autorización XACML, la envía al PDP, recibe una respuesta y hace cumplir la decisión dentro de la respuesta.
  • El Punto de información sobre políticas (PIP) es el componente que puede conectar el PDP a fuentes externas de datos, por ejemplo, un LDAP, una base de datos o un servicio web. El PIP es útil cuando el PEP envía una solicitud, por ejemplo, "¿Puede Alice ver el documento # 12?" y el PDP tiene una política que requiere la edad del usuario. El PDP le preguntará al PIP "dame la edad de Alice" y luego podrá procesar las políticas.
  • El punto de administración de políticas (PAP) es el lugar donde administra toda la solución XACML (define atributos, escribe políticas y configura el PDP).

Lenguaje de marcado de control de acceso extensible - Arquitectura XACML

Así es como se ve su primer 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
    }

 }

Su segundo caso de uso sería:

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

Puede combinar ambos casos de uso en una sola política utilizando referencias:

  policyset global{
    apply firstApplicable
    departmentOne
    departmentTwo
  }

¡Y tu estas listo!

Puede leer más sobre XACML y ALFA en:

David Brossard
fuente
0

Lo que realmente quieres aquí es XACML . Prácticamente te da exactamente lo que quieres. No necesariamente tiene que implementar la arquitectura completa con todos los roles completamente separados ... si solo tiene una sola aplicación, probablemente pueda salirse con la integración de PDP y PEP en su aplicación con balana y el PIP es lo que sea su base de datos de usuario existente es

Ahora, en cualquier lugar de su aplicación necesita autorizar algo, crea una solicitud XACML que tiene el usuario, la acción y el contexto, y el motor XACML tomará la decisión en función de los archivos de política XACML que ha escrito. Estos archivos de políticas se pueden guardar en la base de datos o en el sistema de archivos, o donde desee mantener la configuración. Axiomatics tiene una buena alternativa a la representación XML XACML llamada ALFA que es un poco más fácil de leer que el XML sin procesar, y un complemento Eclipse para generar XML XACML a partir de políticas ALFA.

gregsymons
fuente
1
¿Cómo responde esto a la pregunta que se hace?
mosquito
Está tratando específicamente de implementar un sistema de autorización configurado externamente. XACML es un sistema de autorización externo listo para usar que cubre muy bien su caso de uso específico. Admito que puede que no sea una gran respuesta a la pregunta más general de la ejecución dinámica de código, pero es una buena solución a su pregunta específica.
gregsymons
0

Lo hicimos en mi empresa actual y estamos muy contentos con los resultados.

Nuestras expresiones están escritas en js, e incluso las usamos para restringir los resultados que los usuarios pueden obtener al consultar ElasticSearch.

El truco es asegurarse de que haya suficiente información disponible para tomar la decisión, de modo que realmente pueda escribir cualquier permiso que desee sin cambios de código, pero al mismo tiempo mantenerlo rápido.

No estamos realmente preocupados con los ataques de inyección de código, ya que los permisos están escritos por aquellos que no tienen necesidad de atacar el sistema. Y lo mismo se aplica a los ataques de DOS como elwhile(true) ejemplo. Los administradores del sistema no tienen necesidad de hacer eso, simplemente podrían eliminar los permisos de todos ...

Actualizar:

Algo como XACML parece ser mejor como un punto central de administración de autenticación para una organización. Nuestro caso de uso es ligeramente diferente en el sentido de que nuestros clientes normalmente no tienen un departamento de TI para ejecutar todo eso. Necesitábamos algo autónomo pero tratamos de preservar la mayor flexibilidad posible.

Adagios
fuente