Seguridad en Elasticsearch con el plugin ReadonlyREST

0
1592

Índice de contenidos

1. Introducción

Cuando estamos trabajando con Elasticsearch nos puede resultar útil securizar
las peticiones realizadas a los diferentes índices que tengamos creados y
tener algún control sobre el acceso a los mismos.

Elastic dispone de la extensión de pago X-Pack que proporciona funciones de
seguridad, alertas, monitoreo, informes y gráficos en un paquete fácil de
instalar.

Aunque puede resultar conveniente el pago de esta licencia, puede haber
proyectos que no requieran tantas funciones y en los que resulte más viable el
uso de alguna tecnología open source como el plugin para Elasticsearch
ReadonlyREST.

Concretamente, lo que nos ofrece ReadonlyREST en su versión open source es:

  • Cifrado: acceso a la API de Elasticsearch a través de HTTPS.
  • Autenticación: solicitud de credenciales de acceso.
  • Autorización: declarar grupos de usuarios, permisos y acceso parcial a índices.
  • Control de acceso: lógica de acceso compleja a través de listas de control de acceso (ACL).
  • Registro de auditoría: trazas de las solicitudes de acceso se pueden guardar en ficheros o en índices.
Flujo de una solicitud de búsqueda. Fuente: https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md

Vamos a verlo en acción.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2,6 Ghz Intel Core i7, 32GB DDR4).
  • Sistema Operativo: Mac OS Mojave 10.14.2
  • Docker 18.09.1
  • Postman 6.7.1

3. Caso de prueba

Para nuestro caso, vamos a suponer que tenemos que controlar el acceso a
Elasticsearch para distintos usuarios que se autenticarán con su contraseña de
LDAP. Para ello, vamos a montar un entorno con docker que contenga OpenLDAP,
Elasticsearch y el plugin ReadonlyREST adecuado a nuestra versión de Elastic.
Para hacer las peticiones HTTP utilizaremos Postman.

3.1. Instalación

Lo primero, es descargar el código de aquí:
https://github.com/abarriosautentia/example-readonlyrest-elasticsearch

Una vez que tenemos el código descargado, hay que descomprimir el fichero (si os bajásteis un ZIP) y ejecutar en un terminal (dentro de la carpeta docker):

docker-compose up -d

Si todo ha ido bien, al final de nuestra consola veremos algo así:

    
Creating readonlyrest-ldap ... done
Creating readonlyrest-prepare-ldap ... done
Creating readonlyrest-elasticsearch ... done    

3.2. Explicación

Vamos a pasar a explicar un poco lo que acabamos de hacer repasando el fichero docker-compose.yml que tenemos en la carpeta docker:

LDAP

Hemos montado un LDAP cuya raíz es dc=adictosaltrabajo,dc=com en el que hemos creado los usuarios ‘usuario1’, ‘usuario2’ y ‘usuario3’ que pertenecen a los grupos ‘grupo1’, ‘grupo2’ y ‘grupo3’, respectivamente. La contraseña es la misma que el nombre de usuario.
Para el usuario admin la contraseña es ‘autentia’.

Nuestro LDAP tendrá las siguientes entradas:

      # adictosaltrabajo.com
      dn: dc=adictosaltrabajo,dc=com
      objectClass: top
      objectClass: dcObject
      objectClass: organization
      o: autentia
      dc: adictosaltrabajo

      # admin, adictosaltrabajo.com
      dn: cn=admin,dc=adictosaltrabajo,dc=com
      objectClass: simpleSecurityObject
      objectClass: organizationalRole
      cn: admin
      description: LDAP administrator
      userPassword:: e1NTSEF9SU9tUHI1OTNDMG1NdmV2bmNoRkhEUWQ5clk0U3Y3eWc=

      # Usuarios, adictosaltrabajo.com
      dn: ou=Usuarios,dc=adictosaltrabajo,dc=com
      objectClass: organizationalUnit
      ou: Usuarios

      # usuario1, Usuarios, adictosaltrabajo.com
      dn: uid=usuario1,ou=Usuarios,dc=adictosaltrabajo,dc=com
      objectClass: inetOrgPerson
      uid: usuario1
      sn: Usuario1
      cn: Mi Usuario1
      userPassword:: e1NTSEF9OEc4SkhrTTM2aDVkYUNTRUZNSGVTZ3BoM3lVcUpZRXo=

      # usuario2, Usuarios, adictosaltrabajo.com
      dn: uid=usuario2,ou=Usuarios,dc=adictosaltrabajo,dc=com
      objectClass: inetOrgPerson
      uid: usuario2
      sn: Usuario2
      cn: Mi Usuario2
      userPassword:: e1NTSEF9RzBKK3JXLzN2Ny83VXJ5MWhVdTdqS0dIUjJqbU9VaVo=

      # usuario3, Usuarios, adictosaltrabajo.com
      dn: uid=usuario3,ou=Usuarios,dc=adictosaltrabajo,dc=com
      objectClass: inetOrgPerson
      uid: usuario3
      sn: Usuario3
      cn: Mi Usuario3
      userPassword:: e1NTSEF9MjRObm42aXhleTJOUlZSM1IrblV2WDl4bDJVM2tiRUg=

      # Grupos, adictosaltrabajo.com
      dn: ou=Grupos,dc=adictosaltrabajo,dc=com
      objectClass: organizationalUnit
      ou: Grupos
      description: generic groups branch

      # grupo1, Grupos, adictosaltrabajo.com
      dn: cn=grupo1,ou=Grupos,dc=adictosaltrabajo,dc=com
      objectClass: groupOfUniqueNames
      cn: grupo1
      uniqueMember: uid=usuario1,ou=Usuarios,dc=adictosaltrabajo,dc=com

      # grupo2, Grupos, adictosaltrabajo.com
      dn: cn=grupo2,ou=Grupos,dc=adictosaltrabajo,dc=com
      objectClass: groupOfUniqueNames
      cn: grupo2
      uniqueMember: uid=usuario2,ou=Usuarios,dc=adictosaltrabajo,dc=com

      # grupo3, Grupos, adictosaltrabajo.com
      dn: cn=grupo3,ou=Grupos,dc=adictosaltrabajo,dc=com
      objectClass: groupOfUniqueNames
      cn: grupo3
      uniqueMember: uid=usuario3,ou=Usuarios,dc=adictosaltrabajo,dc=com
Elasticsearch

Tenemos montado un Elasticsearch (versión 6.5.4) con el plugin ReadonlyREST instalado (versión 1.16.33_es6.5.4), en el que hemos deshabilitado las características de seguridad de Elasticsearch (para evitar conflictos con el plugin) y hemos habilitado SSL para disponer de conexión HTTPS. Para ello, hemos tenido que crear un almacen de claves de prueba (keystore.jks).

ReadonlyREST

En el fichero readonlyrest.yml está la parte más interesante ya que es donde hemos configurado el control de acceso a nuestros índices y el acceso a LDAP.

Lo primero que hemos hecho es activar la propiedad audit_collector para que los errores de acceso se guarden en un índice de Elasticsearch. El nombre por defecto del índice es readonlyrest_audit-YYYY-MM-DD.

Habilitamos el cifrado SSL para poder utilizar el protocolo HTTPS y configuramos el keystore de pruebas que tenemos generado con un certificado autofirmado. Esto es sólo para pruebas, la generación de certificados para producción debe estar validada por una Autoridad certificada (CA). Podéis leer más información en este tutorial.

Las reglas para el control de acceso (access_control_rules) se dividen en bloques que contienen un listado de reglas. Estos bloques se aplican en orden secuencial, es decir, se va comprobando cada bloque de reglas de arriba a abajo hasta que se cumple un bloque completo de reglas. Si no se encuentra ningún bloque en el que se cumpla todo el listado de reglas, la petición se rechaza.

En nuestro caso, hemos creado 4 bloques de reglas:

  1. Los usuarios del grupo1, que se autentiquen correctamente contra LDAP, pueden crear índices e introducir datos (con bulk) en los índices que comiencen con el nombre «my_index».
  2. Los usuarios del grupo2, que se autentiquen correctamente contra LDAP, pueden consultar datos de los índices que comiencen con el nombre «my_index».
  3. Los usuarios del grupo3, que se autentiquen correctamente contra LDAP, pueden gestionar el cluster de Elasticsearch.
  4. Cualquier usuario, que se autentique correctamente contra LDAP, puede consultar los índices de auditoría (readonlyrest_audit-*).
    
      access_control_rules:
        - name: 'Create and Bulk index access to all indices my_index* from users belong to grupo1'
          ldap_authentication: 'my_ldap'
          ldap_authorization:
            name: 'my_ldap' # ldap name from 'ldaps' section
            groups: ['grupo1'] # group within 'ou=Grupos,dc=adictosaltrabajo,dc=com'
          indices: ['my_index*']
          actions: ['indices:admin/create', 'indices:data/write/bulk']

        - name: 'Read access to all indices my_index* from users belong to grupo2'
          ldap_authentication: 'my_ldap'
          ldap_authorization:
            name: 'my_ldap' # ldap name from 'ldaps' section
            groups: ['grupo2'] # group within 'ou=Grupos,dc=adictosaltrabajo,dc=com'
          indices: ['my_index*']
          actions: ['indices:data/read/*']

        - name: 'Cluster access from users belong to grupo3'
          ldap_authentication: 'my_ldap'
          ldap_authorization:
            name: 'my_ldap' # ldap name from 'ldaps' section
            groups: ['grupo3'] # group within 'ou=Grupos,dc=adictosaltrabajo,dc=com'
          actions: ['cluster:*']

        - name: 'Read access to audit logs indices readonlyrest_audit-*'
          ldap_authentication: 'my_ldap'
          indices: ['readonlyrest_audit-*']
          actions: ['indices:data/read/*']

3.3. Probando el acceso

Abrimos Postman y vamos a comprobar que nuestras reglas de acceso se cumplen lanzando varias peticiones.

Antes de comenzar, debemos deshabilitar la verificación de certificados SSL para que no nos dé problemas el certificado autofirmado que estamos usando para nuestras pruebas. Lo hacemos desde:

Preferences -> General -> SSL certificate verification -> OFF

Para todas las peticiones, en la pestaña Auth seleccionaremos Basic Auth, donde introduciremos los usuarios de pruebas:

y en la pestaña Headers añadiremos Content-Type -> application/json:

Vamos con nuestra primera prueba donde crearemos el índice ‘my_index1’ con el usuario1, que tiene permisos para crear un índice con este nombre. Para ello lanzamos el siguiente PUT:

https://localhost:9200/my_index1

y en la respuesta recibimos el ACK correctamente:

Ahora vamos a insertar documentos en el índice con este mismo usuario. Lanzamos el siguiente PUT:

https://localhost:9200/_bulk

y en la respuesta recibimos la confirmación con el código 201 (Created):

Para consultar los datos del índice, debemos utilizar el usuario2 que tiene permisos de lectura sobre este índice. Lanzamos el siguiente GET:

https://localhost:9200/my_index1/_search

y recibimos la respuesta con el documento que acabamos de crear:

Si lo intentamos con el usuario1, que no tiene permiso de lectura, o metemos mal el usuario o la contraseña, nos devolverá un error 401 (Unauthorized):

Ahora vamos a consultar información del cluster con el usuario3. Lanzamos el siguiente GET:

https://localhost:9200/_cluster/state

y recibimos correctamente la información del cluster:

Por último, con cualquiera de los usuarios que tenemos podemos consultar los logs que se han cargado en el índice readonlyrest_audit-YYYY-MM-DD. Lanzamos el siguiente GET:

https://localhost:9200/readonlyrest_audit-*/_search

y vemos la auditoría que se ha guardado:

Podemos ver que se ha almacenado un documento en el índice para cada petición realizada, con mucha información que podemos explotar. Podemos destacar el campo acl_history donde se guardan los bloques de reglas que se han procesado para esa petición y el resultado de evaluar (en orden) cada una de ellas.

4. Conclusiones

En este tutorial hemos visto que tenemos alternativas open source para proteger el acceso a nuestro índices, más allá de la herramienta oficial X-Pack y sin necesidad de configurar ningún proxy.

Por medio del plugin ReadonlyREST para Elasticsearch hemos sido capaces de securizar nuestro Elasticsearch conectándolo a un LDAP de manera sencilla, pero ofrece más opciones como autenticación interna básica, delegar en un proxy, autenticación externa básica, JWT. También existen versiones de pago de ReadonlyREST que ofrecen más características.

La posibilidad de explotar los logs de acceso es un complemento muy útil a la hora de conocer cómo se están aplicando las reglas que hemos definido.

Espero que os haya resultado útil.

5. Referencias

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad