Docker Config: Thehive5 with Cortex and n8n

By Adrian | June 20, 2022

I’ll start by saying, that I have done these sorts of posts in the past where I have stood up TheHive and reverse proxies etc using a docker-compose file so the basic configuration etc is going to be heavily borrowed except for some minor tweaks.

I am still old school so this isnt a configuration you would want to run for mission critical services, however there is a guide for how to use Docker in Production. If you’re like me, I like to stand up individual servers for each service, but for lab/test environments having all the configuration in a single docker-compose.yml file for testing, prototyping etc is a neat way to quickly stand everything up.

For this particular setup, my docker-compose file contains the following:

  • TheHive5 (requires: cassandra, minio, elasticsearch)
  • Cortex3 (requires: elasticsearch)
  • Traefik (for reverse proxy/let’s encrypt certificates via AWS Route53)
  • n8n (workflow automation)

All up, 7 containers setup on a rather modest 2 core, 4Gb RAM virtual machine. Ideal for what iI’m doing.

Theres an associated .env file that contains usernames/passwords and general environment variables. I am hoping once I can clean the config files up some more that I can share it on TheHive-Project/Docker-Templates.

Until then here are the files:

.env

# AWS Route53 credentials
AWS_HOSTED_ZONE_ID=[YOUR ROUTE53 DNS Zone ID]
AWS_ACCESS_KEY_ID=[AWS ACCESS KEY FOR A ROUTE53 USER]
AWS_SECRET_ACCESS_KEY=[AWS SECRET ACCESS KEY FOR A ROUTE53 USER]

# Lets Encrypt configuration
LE_EMAIL=[YOUR EMAIL ADDRESS FOR LETS ENCRYPT]

# Traefic
TRAEFIK_HOST=[THE FQDN FOR TRAEFIK CONSOLE]

# TheHive configuration
THEHIVE_HOST=[THE FQDN FOR THEHIVE]
THEHIVE_SECRET="changemetosomethingmoresecure"

# Minio configuration
MINIO_HOST=[THE FQDN FOR MINIO]
MINIO_ROOT_USER=miniouser
MINIO_ROOT_PASSWORD=miniopassword
MINIO_S3_ACCESS_KEY=minioaccesskey
MINIO_S3_SECRET_KEY=miniosecretkey

# Cortex configuration
CORTEX_HOST=[THE FQDN FOR CORTEX]
# CORTEX_KEY=[API KEY OF CORTEX USER]

# N8N configuration
DATA_FOLDER=/home/user/n8n/
N8N_HOST=[THE FQDN FOR N8N]
N8N_BASIC_AUTH_USER=user
N8N_BASIC_AUTH_PASSWORD=changeme
N8N_GENERIC_TIMEZONE=Australia/Melbourne

docker-compose.yml


version: "3"
services:
  thehive:
    image: strangebee/thehive:5.0.7-1
    container_name: thehive5
    depends_on:
      - cassandra
      - elasticsearch
      - minio
    mem_limit: 1500m
    ports:
      - "9000:9000"
    environment:
      - JVM_OPTS="-Xms1024M -Xmx1024M"
    command:
      - --secret
      - ${THEHIVE_SECRET}
      - "--cql-hostnames"
      - "cassandra"
      - "--index-backend"
      - "elasticsearch"
      - "--es-hostnames"
      - "elasticsearch"
      - "--s3-endpoint"
      - "http://minio:9000"
      - "--s3-access-key"
      - ${MINIO_S3_ACCESS_KEY}
      - "--s3-secret-key"
      - ${MINIO_S3_SECRET_KEY}
      - "--s3-use-path-access-style"
      - "--cortex-port"
      - "9001"
      - "--cortex-keys"
      - "${CORTEX_KEY}"

    networks:
      - proxy
      - backend

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.thehive.entrypoints=https"
      - "traefik.http.routers.thehive.rule=Host(`${THEHIVE_HOST}`)"
      - "traefik.http.routers.thehive.service=thehive"
      - "traefik.http.routers.thehive.tls=true"
      - "traefik.http.services.thehive.loadbalancer.server.port=9000"
      - "traefik.http.routers.thehive.tls.certresolver=mytlschallenge"

      - "traefik.http.routers.thehive_http.entrypoints=http"
      - "traefik.http.routers.thehive_http.rule=Host(`${THEHIVE_HOST}`)"
      - "traefik.http.routers.thehive_http.middlewares=traefik-redirectscheme"
      - "traefik.http.middlewares.traefik-redirectscheme.redirectscheme.scheme=https"

  cassandra:
    image: 'cassandra:4'
    container_name: cassandra4
    mem_limit: 2000m
    ports:
      - "9042:9042"
    environment:
      - CASSANDRA_CLUSTER_NAME=TheHive
    volumes:
      - cassandradata:/var/lib/cassandra
    networks:
      - backend

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.16.2
    container_name: elasticsearch7
    mem_limit: 512m
    ports:
      - "9200:9200"
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    volumes:
      - elasticsearchdata:/usr/share/elasticsearch/data
    networks:
      - backend

  minio:
    image: quay.io/minio/minio
    container_name: minio
    command: ["minio", "server", "/data", "--console-address", ":9001"]
    environment:
      - MINIO_ROOT_USER=${MINIO_ROOT_USER}
      - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
    ports:
      - "9001:9001"
    volumes:
      - "miniodata:/data"
    networks:
      - proxy
      - backend

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.minio.entrypoints=https"
      - "traefik.http.routers.minio.rule=Host(`${MINIO_HOST}`)"
      - "traefik.http.routers.minio.service=minio"
      - "traefik.http.routers.minio.tls=true"
      - "traefik.http.services.minio.loadbalancer.server.port=9001"
      - "traefik.http.routers.minio.tls.certresolver=mytlschallenge"

      - "traefik.http.routers.minio_http.entrypoints=http"
      - "traefik.http.routers.minio_http.rule=Host(`${MINIO_HOST}`)"
      - "traefik.http.routers.minio_http.middlewares=traefik-redirectscheme"
      - "traefik.http.middlewares.traefik-redirectscheme.redirectscheme.scheme=https"

  cortex:
    image: thehiveproject/cortex:latest
    container_name: cortex3
    depends_on:
      - elasticsearch
      - traefik
    networks:
      - proxy
      - backend
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock'
      - './vol/cortex/jobs:/opt/cortex/jobs'
      - /tmp:/tmp
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.cortex.entrypoints=https"
      - "traefik.http.routers.cortex.rule=Host(`${CORTEX_HOST}`)"
      - "traefik.http.routers.cortex.service=cortex"
      - "traefik.http.routers.cortex.tls=true"
      - "traefik.http.services.cortex.loadbalancer.server.port=9001"
      - "traefik.http.routers.cortex.tls.certresolver=mytlschallenge"

      - "traefik.http.routers.cortex_http.entrypoints=http"
      - "traefik.http.routers.cortex_http.rule=Host(`${CORTEX_HOST}`)"
      - "traefik.http.routers.cortex_http.middlewares=traefik-redirectscheme"
      - "traefik.http.middlewares.traefik-redirectscheme.redirectscheme.scheme=https"

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    hostname: ${N8N_HOST}
    depends_on:
      - traefik
    networks:
      - proxy
      - backend
    restart: always
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER
      - N8N_BASIC_AUTH_PASSWORD
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=http://${N8N_HOST}:5678
      - GENERIC_TIMEZONE=${N8N_GENERIC_TIMEZONE}
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.entrypoints=https"
      - "traefik.http.routers.n8n.rule=Host(`${N8N_HOST}`)"
      - "traefik.http.routers.n8n.service=n8n"
      - "traefik.http.routers.n8n.tls=true"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
      - "traefik.http.routers.n8n.tls.certresolver=mytlschallenge"

      - "traefik.http.routers.n8n_http.entrypoints=http"
      - "traefik.http.routers.n8n_http.rule=Host(`${N8N_HOST}`)"
      - "traefik.http.routers.n8n_http.middlewares=traefik-redirectscheme"
      - "traefik.http.middlewares.traefik-redirectscheme.redirectscheme.scheme=https"
    volumes:
      - /root/n8n/.n8n:/home/node/.n8n

  traefik:
    image: traefik:latest
    environment:
      - AWS_HOSTED_ZONE_ID=${AWS_HOSTED_ZONE_ID}
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
    restart: always
    container_name: traefik
    ports:
      - "80:80"
      - "443:443"
    command:
      - --api.insecure=true # set to 'false' on production
      - --api.dashboard=true
      - --api.debug=false
      - --log.level=DEBUG
      - --providers.docker=true
      - --providers.docker.swarmMode=false
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=proxy
      - --entrypoints.http.address=:80
      - --entrypoints.https.address=:443
      - --certificatesResolvers.mytlschallenge.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory # Generates LE test certificates.  Can be removed for production
      - --certificatesResolvers.mytlschallenge.acme.dnsChallenge=true
      - --certificatesResolvers.mytlschallenge.acme.dnsChallenge.provider=route53
      - --certificatesresolvers.mytlschallenge.acme.email=${LE_EMAIL}
      - --certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json

    volumes:
      - "letsencrypt:/letsencrypt"
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - proxy

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.entrypoints=https"
      - "traefik.http.routers.api.rule=Host(`${TRAEFIK_HOST}`)"
      - "traefik.http.routers.api.service=api@internal"
      - "traefik.http.routers.api.tls=true"
      - "traefik.http.services.api.loadbalancer.server.port=8080"
      - "traefik.http.routers.api.tls.certresolver=mytlschallenge"

      - "traefik.http.routers.api_http.entrypoints=http"
      - "traefik.http.routers.api_http.rule=Host(`${TRAEFIK_HOST}`)"
      - "traefik.http.routers.api_http.middlewares=traefik-redirectscheme"
      - "traefik.http.middlewares.traefik-redirectscheme.redirectscheme.scheme=https"

networks:
  backend:
  proxy:
    external: true

volumes:
  miniodata:
  cassandradata:
  elasticsearchdata:
  letsencrypt:

Stand it up

Once you have created these 2 files you can perform a sudo docker-compose up -d.

If you see the following error, run the command as noted to create the required Docker network.

ERROR: Network proxy declared as external, but could not be found. Please create the network manually using `docker network create proxy` and try again.

After a few minitues, your sevices will be available to access.

You will notice that the certificates will come as invalid (Issued by Artificial Apricot R3 - Lets Encrypt). This is because the docker-compose file is configured to use the Lets Encrypt staging certificate.

After you have successfully generated test certificates, update the configuration and comment out the line for certificatesResolvers.mytlschallenge.acme.caServer and restart the services.

The main reason to use the staging certificate first to ensure all the configuration is ok. This saves you from hitting any imposed throttle limits on Lets Encrypt if your config is bad.

Initial Setup

Once all the services have started up, I suggest to do the following:

Traefik

  • Ensure you can access the dashboard

N8N

  • Ensure you can access workflow

MINIO

  • Ensure you can access and login

Cortex

  • Update Database on first access
  • Create an Admin user
  • Create a new Org and admin user for that org
  • Set the password for the new user
  • Create an API Key and update the .env file with it
  • Login with the new user and configure at least 1 Analyser (ie: DShield makes a good test as it request no additional configuration)
  • Run an analyzer and ensure it completes ok

TheHive5

  • Initial login with admin@thehive.local/secret
  • Create a new Org
  • Create an user with org admin permissions and set a password
  • Create an user with analyst permissions and set a password
  • Test logins to TheHive with these accounts

I am hoping to expand on the workflow automation side in the near future.