Open Distro for Elasticsearch Utils - Part I
Vistazo general
Vamos a tratar de preparar un entorno de pruebas con Elasticsearch.
- Para simular un entorno lo mas real posible vamos a levantar un cluster de Elasticsearch de dos nodos con autenticación y cifrado SSL.
- Una forma muy sencilla de hacerlo es usando la distribución de OpenDistro for Elasticsearch.
- Usaremos Docker Compose para levantar el stack completo y partiremos del fichero
docker-compose.yml
proporcionado en la documentacion oficial. - Lo que haremos es configurar los certificados de cero y se los proporcionaremos a cada instancia via volumenes de docker. De esta forma podremos usarlos en otros clientes y podremos, entre otras cosas, generar otro tipo de key stores, como por ejemplo de tipo
JKS
muy tipico en clientes que usan la JVM.
Indice
- Generar certificados
- elasticsearch.yml
- kibana.yml
- docker-compose.yml
- Añadir entrada a /etc/hosts
- Prueba de funcionamiento
Generar certificados
Seguiremos la documentacion oficial de Open Distro for Elasticsearch para la generacion de certificados
Opendistro Docs
Generamos la private key
openssl genrsa -out root-ca-key.pem 2048
Generamos el root certificate
Creamos un root-ca.pem
que usaremos como CA para cualquier cliente de nuestro Elasticsearch Open Distro,
ya sea un cliente de linea de comandos, en nuestro caso curl
o para un cliente escrito en Python, etc.
Para un cliente JVM en Scala, Java, Kotlin.. se suele usar para el handshake de la negociacion entre servidores un Java Key Store. En una posterior entrada del blog veremos como crear un
truststore.jks
a partir de nuestraroot-ca-key.pem
para poder usar con cualquier cliente JVM.
Para crear nuestro certificado root-ca.pem
openssl req -new -x509 -days 6552 -sha256 -key root-ca-key.pem -out root-ca.pem
Los 6552 dias son porque desde hoy (11/02/2020) hasta la fecha (19/01/2038) hay 6552 dias. Por tanto la fecha de expiracion del certificado será el dia 19/01/2038. Esta fecha es famosa por el Y2K38 Problem pero puedes poner los dias que quieras.
Rellenamos la informacion solicitada
Country Name (2 letter code) []:ES
State or Province Name (full name) []:Madrid
Locality Name (eg, city) []:Madrid
Organization Name (eg, company) []:ORG
Organizational Unit Name (eg, section) []:UNIT
Common Name (eg, fully qualified host name) []:ilittleangel
Email Address []:
Generamos los node certificates
-
Generaremos certificados para dos nodos de Elasticsearch (esnode1, esnode2).
- Primero generamos una nueva key temporal que usaremos para generar los certificados de ambos nodos
openssl genrsa -out node-key-temp.pem 2048
- Convertimos la key temporal al formato PKCS#8 para el nodo 1 y la llamaremos
esnode1-key.pem
openssl pkcs8 -inform PEM -outform PEM -in node-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out esnode1-key.pem
- Creamos un CSR (certificate signing request)
openssl req -new -key esnode1-key.pem -out node.csr
- Rellenamos la informacion solicitada para el nodo 1. En la opcion
Common Name
hay que introducir el hostname del nodo 1 de elasticsearch, que en este caso será el que elijamos en la propertycontainer_name
del docker-compose. (email
ypassword
pueden quedar vacios)Country Name (2 letter code) []:ES State or Province Name (full name) []:Madrid Locality Name (eg, city) []:Madrid Organization Name (eg, company) []:ORG Organizational Unit Name (eg, section) []:UNIT Common Name (eg, fully qualified host name) []:es-opendistro-node1 Email Address []: A challenge password []:
- Por último, generamos el certificado para el nodo 1
openssl x509 -req -in node.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -days 6552 -out esnode1.pem
- Eliminamos los ficheros que ya no usaremos
rm node-key-temp.pem rm node.csr rm root-ca.srl
- Repetimos los pasos para generar los certificados del nodo 2
elasticsearch.yml
Ahora toca editar el fichero de configuración de Elasticsearch. Para obtener este fichero puedes copiar y pegar el proporcionado aquí, el cual ya tiene la configuración correcta. Tambien se puede obtener de las siguientes formas:
-
a traves del template oficial de OpenDistro en github
-
levantando un contenedor docker con Elaticsearch a partir de la imagen
amazon/opendistro-for-elasticsearch
y navegar por el filesystem local del contendor hasta el fichero para copiar y pegar el contenidodocker run -p 9200:9200 -p 9600:9600 -e "discovery.type=single-node" amazon/opendistro-for-elasticsearch:1.4.0 docker cp <elasticsearch-container-name>:/usr/share/elasticsearch/config/elasticsearch.yml ~/.
En el fichero elasticsearch.yml
tenemos toda la configuración inicial que necesita Elasticsearch para arrancar. Entre esta configuración está la de seguridad y cifrado ssl tanto para la capa de transporte como para el API HTTP que expone el propio Elasticsearch.
cluster.name: "docker-cluster"
network.host: 0.0.0.0
opendistro_security.ssl.transport.pemcert_filepath: esnode.pem
opendistro_security.ssl.transport.pemkey_filepath: esnode-key.pem
opendistro_security.ssl.transport.pemtrustedcas_filepath: root-ca.pem
opendistro_security.ssl.transport.enforce_hostname_verification: false
opendistro_security.ssl.http.enabled: true
opendistro_security.ssl.http.pemcert_filepath: esnode.pem
opendistro_security.ssl.http.pemkey_filepath: esnode-key.pem
opendistro_security.ssl.http.pemtrustedcas_filepath: root-ca.pem
opendistro_security.allow_unsafe_democertificates: true
opendistro_security.allow_default_init_securityindex: true
opendistro_security.nodes_dn:
- 'CN=es-opendistro-node1,OU=UNIT,O=ORG,L=Madrid,ST=Madrid,C=ES'
- 'CN=es-opendistro-node2,OU=UNIT,O=ORG,L=Madrid,ST=Madrid,C=ES'
opendistro_security.authcz.admin_dn:
- CN=kirk,OU=client,O=client,L=test, C=de
opendistro_security.audit.type: internal_elasticsearch
opendistro_security.enable_snapshot_restore_privilege: true
opendistro_security.check_snapshot_restore_write_privileges: true
opendistro_security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]
cluster.routing.allocation.disk.threshold_enabled: false
node.max_local_storage_nodes: 3
Tener en cuenta lo siguiente:
- Los datos introducidos a la hora de crear los certificados del nodo 1 y 2 serán los que tendremos que setear en nuestro
elasticsearch.yml
en la propiedadopendistro_security.nodes_dn
. En esta propiedad introduciremos tantas entradas como nodos o domain names de Elasticsearch tengamos, en nuestro caso 2. - El fichero se montará en todos los nodos del cluster de Elasticsearch y este será el mismo para todos, aunque los certificados que montaremos no. Lo que haremos es montar ficheros con el mismo nombre pero con contenido diferente. En el
docker-compose.yml
se ve como se hace eso en la parte devolumes
de cada servicio.
kibana.yml
El fichero kibana.yml
podriamos dejar el que monta la distribución por defecto, ya que está activada una opcion que permite la no comprobación de certificados. Pero si queremos hacerlo correctamente, tenemos que montar el fichero con la CA que usaremos para los clientes de nuestro Elasticsearch OpenDistro, dentro del contenedor de Kibana. Y decirle a Kibana donde se encuentra este certificado root-ca.pem
mediante la propiedad elasticsearch.ssl.certificateAuthorities
.
server.name: kibana
server.host: "0"
elasticsearch.hosts: https://localhost:9200
elasticsearch.ssl.verificationMode: full
elasticsearch.ssl.certificateAuthorities: [ "/usr/share/kibana/config/root-ca.pem" ]
elasticsearch.username: kibanaserver
elasticsearch.password: kibanaserver
elasticsearch.requestHeadersWhitelist: ["securitytenant","Authorization"]
opendistro_security.multitenancy.enabled: true
opendistro_security.multitenancy.tenants.preferred: ["Private", "Global"]
opendistro_security.readonly_mode.roles: ["kibana_read_only"]
docker-compose.yml
Tenemos el docker-compose oficial en la documentacion de Open Distro.
Para nuestro ejemplo, tendríamos que añadir lo siguiente:
- el montaje de los ficheros
elasticsearch.yml
ykibana.yml
via volumenes de docker - el montaje de los ficheros de certificados via volúmenes de docker
- por utlimo y aunque no es estrictamente necesario, por facilitar el uso, fijar las ips que asigna docker a los nodos de Elasticsearch, mediante la propiedad de red
ipv4_address
y en la seccion denetworks
la propiedadipam
version: "3"
services:
kibana:
# https://opendistro.github.io/for-elasticsearch-docs/docs/troubleshoot/#java-error-during-startup
container_name: kibana-opendistro
image: amazon/opendistro-for-elasticsearch-kibana:1.4.0
ports:
- 5601:5601
depends_on:
- elasticsearch1
environment:
ELASTICSEARCH_URL: https://es-opendistro-node1:9200
ELASTICSEARCH_HOSTS: https://es-opendistro-node1:9200
volumes:
- ./opendistro/config/kibana.yml:/usr/share/kibana/config/kibana.yml
- ./opendistro/config/root-ca.pem:/usr/share/kibana/config/root-ca.pem
networks:
- elastic-net
elasticsearch1:
container_name: es-opendistro-node1
image: amazon/opendistro-for-elasticsearch:1.4.0
ports:
- 9200:9200
- 9600:9600 # required for Performance Analyzer
environment:
- cluster.name=opendistro-cluster
- node.name=es-opendistro-node1
- discovery.seed_hosts=es-opendistro-node1,es-opendistro-node2
- cluster.initial_master_nodes=es-opendistro-node1,es-opendistro-node2
- bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems
hard: 65536
volumes:
- elasticsearch-data1:/usr/share/elasticsearch/data
- ./opendistro/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./opendistro/config/esnode1.pem:/usr/share/elasticsearch/config/esnode.pem
- ./opendistro/config/esnode1-key.pem:/usr/share/elasticsearch/config/esnode-key.pem
- ./opendistro/config/root-ca.pem:/usr/share/elasticsearch/config/root-ca.pem
networks:
elastic-net:
ipv4_address: 172.23.0.2
elasticsearch2:
container_name: es-opendistro-node2
image: amazon/opendistro-for-elasticsearch:1.4.0
environment:
- cluster.name=opendistro-cluster
- node.name=es-opendistro-node2
- discovery.seed_hosts=es-opendistro-node1,es-opendistro-node2
- cluster.initial_master_nodes=es-opendistro-node1,es-opendistro-node2
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- elasticsearch-data2:/usr/share/elasticsearch/data
- ./opendistro/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./opendistro/config/esnode2.pem:/usr/share/elasticsearch/config/esnode.pem
- ./opendistro/config/esnode2-key.pem:/usr/share/elasticsearch/config/esnode-key.pem
- ./opendistro/config/root-ca.pem:/usr/share/elasticsearch/config/root-ca.pem
networks:
elastic-net:
ipv4_address: 172.23.0.3
volumes:
elasticsearch-data1:
elasticsearch-data2:
networks:
elastic-net:
ipam:
driver: default
config:
- subnet: 172.23.0.0/16
Añadir entrada a /etc/hosts
Como hemos fijado las ip’s que se usarán para levantar los dos nodos de elasticsearch, podemos mapear el hostname que usaremos desde fuera de la red interna que crea docker para enviar peticiones al elastic nodo 1. Lo haremos añadiendo la siguiente entrada
echo '172.23.0.2 es-opendistro-node1' | sudo tee -a /etc/hosts
echo '172.23.0.3 es-opendistro-node2' | sudo tee -a /etc/hosts
Prueba de funcionamiento
curl --cacert root-ca.pem \
-H "Authorization: Basic $(echo -n admin:admin | base64)" \
-XGET "https://es-opendistro-node1:9200"