Goals:

  • single node elasticsearch
  • single node kibana
  • password for all accounts
  • https between all components
  • behind traefik
  • future post: collect network logs (routers)
  • future post: collect application logs (web servers, dns servers, docker)
  • future post: collect application metrics
  • future post: correlate with threat intelligence

Create compose file

version: '3'

services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.16.3
    container_name: elastic_es
    restart: always
    env_file:
      - ./.env
    environment:
      ES_JAVA_OPTS: "-Xms2g -Xmx2g"
      node.name: "es"
      discovery.type: "single-node"
      bootstrap.memory_lock: "true"
      # minimal security
      xpack.security.enabled: "true"
      # no encryption on internode communication
      xpack.security.transport.ssl.enabled: "false"
      # https traffic
      xpack.security.http.ssl.enabled: "true"
      xpack.security.http.ssl.key: "${CERTS_DIR}/es.key"
      xpack.security.http.ssl.certificate: "${CERTS_DIR}/es_chain.crt"
      xpack.security.http.ssl.certificate_authorities: "${CERTS_DIR}/ca.crt"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    networks:
      - reverseproxy
      - default
    volumes:
      - data:/usr/share/elasticsearch/data
      - certs:${CERTS_DIR}:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.elastic.rule=Host(`elasticsearch.foobar.com`)"
      - "traefik.http.routers.elastic.service=elastic"
      - "traefik.http.routers.elastic.tls=true"
      - "traefik.http.routers.elastic.tls.certresolver=le"
      - "traefik.http.routers.elastic.entrypoints=websecure"
      - "traefik.http.services.elastic.loadbalancer.server.port=9200"
      - "traefik.http.services.elastic.loadbalancer.server.scheme=https"
      - "traefik.http.services.elastic.loadbalancer.serversTransport=elastic"
      - "traefik.http.serversTransports.elastic.serverName=es"
      - "traefik.http.serversTransports.elastic.insecureSkipVerify=true"
    deploy:
      resources:
        limits:
          cpus: "4.0"
          memory: 4000M
    memswap_limit: 4000M

  kibana:
    image: docker.elastic.co/kibana/kibana:7.16.3
    container_name: elastic_kibana
    restart: always
    depends_on:
      - es
    env_file:
      - ./.env
      - ./.env.kibana
    environment:
      - ELASTICSEARCH_URL="https://es:9200"
      - ELASTICSEARCH_HOSTS=["https://es:9200"]
      # minimal security: defined in environment files
      # kibana has to trust elasticsearch certificate
      - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES="${CERTS_DIR}/ca.crt"
      # https traffic between other components and kibana
      - SERVER_SSL_ENABLED=true
      - SERVER_SSL_KEY=${CERTS_DIR}/kibana.key
      - SERVER_SSL_CERTIFICATE=${CERTS_DIR}/kibana_chain.crt
      - SERVER_PUBLICBASEURL=https://kibana.foobar.com
    networks:
      - reverseproxy
      - default
    volumes:
      - certs:${CERTS_DIR}:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.kibana.rule=Host(`kibana.foobar.com`)"
      - "traefik.http.routers.kibana.service=kibana"
      - "traefik.http.routers.kibana.tls=true"
      - "traefik.http.routers.kibana.tls.certresolver=le"
      - "traefik.http.routers.kibana.entrypoints=websecure"
      - "traefik.http.services.kibana.loadbalancer.server.port=5601"
      - "traefik.http.services.kibana.loadbalancer.server.scheme=https"
      - "traefik.http.services.kibana.loadbalancer.serversTransport=kibana"
      - "traefik.http.serversTransports.kibana.serverName=kibana"
      - "traefik.http.serversTransports.kibana.insecureSkipVerify=true"
    deploy:
      resources:
        limits:
          cpus: "4.0"
          memory: 4000M
    memswap_limit: 4000M

volumes:
  data:
  certs:
    name: elastic_certs
    external: true

networks:
  reverseproxy:
    external: true 

Create a file named .env with the following content:

COMPOSE_PROJECT_NAME=elastic
CERTS_DIR=/usr/share/elasticsearch/config/certificates

Create SSL CA and certificates for Elasticsearch and Kibana

Create a volume named elastic_certs, create the certificates using a temporary container and change the ownership:

docker volume create elastic_certs
docker run -it --rm -v elastic_certs:/certs alpine:3.15.0 sh

apk update && apk add openssl && cd /certs

# create the CA
openssl req -x509 -nodes -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -keyout ca.key -out ca.crt -days 3652 -subj "/C=LU/ST=Luxembourg/L=Luxembourg/O=Xentoo/CN=elastic_ca" -extensions v3_ca

# create csr for elastic node
openssl req -nodes -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -keyout es.key -out es.csr -subj "/C=LU/ST=Luxembourg/L=Luxembourg/O=Xentoo/CN=es" -addext "subjectAltName=DNS:es" -extensions v3_req

# sign the certificate
openssl x509 -req -days 3652 -CA ca.crt -CAkey ca.key -CAcreateserial -extfile <(printf "subjectAltName=DNS:es") -in es.csr -out es.crt

# repeat for kibana
openssl req -nodes -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -keyout kibana.key -out kibana.csr -subj "/C=LU/ST=Luxembourg/L=Luxembourg/O=Xentoo/CN=es" -addext "subjectAltName=DNS:kibana" -extensions v3_req
openssl x509 -req -days 3652 -CA ca.crt -CAkey ca.key -CAcreateserial -extfile <(printf "subjectAltName=DNS:kibana") -in kibana.csr -out kibana.crt

# create certificate chains
cat es.crt ca.crt > es_chain.crt
cat kibana.crt ca.crt > kibana_chain.crt

# change ownership of certificates for elasticsearch & kibana
chown 1000:1000 es* kibana*

exit

In a production environment, you should sign the certificates with your existing CA, but for testing, this is enough.

Define passwords for default users

Start elasticsearch node: docker compose up es -d

Set the passwords for the default users:

docker exec -it elastic_es elasticsearch-setup-passwords auto --url https://es:9200 --batch

Save the passwords in a safe location, you will need them later to connect the various components to Elasticsearch.

Configure Kibana

Create a file named .env.kibana and with the following content, use the password from previous step:

ELASTICSEARCH_USERNAME=kibana_system
ELASTICSEARCH_PASSWORD=password

Start kibana container: docker compose up kibana -d

At this point, you should be able to connect to Kibana with username elastic.