Í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.
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:
- 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».
- Los usuarios del grupo2, que se autentiquen correctamente contra LDAP, pueden consultar datos de los índices que comiencen con el nombre «my_index».
- Los usuarios del grupo3, que se autentiquen correctamente contra LDAP, pueden gestionar el cluster de Elasticsearch.
- 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.