Déploiement d’une stack ELK en mode Swarm

Publié par Adrien Pavone le

Bonjour à tous, dans cet article nous allons vous présenter comment déployer la solution open source ELK en mode Swarm.

Vous verrez les questions que nous nous sommes posées afin d’en déterminer l’infrastructure, le dimensionnement, mais aussi comment déployer la Stack et la rendre hautement disponible.

Qu’est-ce que ELK ?

Aujourd’hui appelé Elastic Stack, « ELK » est l’acronyme de trois projets open source : Elasticsearch, Logstash et Kibana.

Elasticsearch

Elasticsearch est un moteur de recherche et d’analyse RESTful distribué, conçu pour répondre à une multitude de cas d’utilisation.

Qu’il s’agisse de données structurées ou non structurées, de données géographiques ou d’indicateurs, avec Elasticsearch, il est possible de lancer différents types de recherches, ou encore les associer entre elles.

Logstash

Logstash est un pipeline open source côté serveur, destiné au traitement des données.

Sa mission ? Ingérer simultanément des données provenant d’une multitude de sources, puis les transformer et les envoyer vers un système de stockage.

Le pipeline de traitement des événements Logstash comprend trois étapes :

entrées -> filtres -> sorties

Les entrées génèrent des événements, les filtres les modifient et les sorties les expédient ailleurs. Les entrées et les sorties prennent en charge des codecs qui permettent de coder ou de décoder les données à l’entrée ou à la sortie du pipeline sans avoir à utiliser un filtre séparé.

Kibana

Kibana permet de visualiser les données Elasticsearch et de naviguer dans la Suite Elastic.

Qu’est-ce que Swarm ?

Swarm est un groupe de machines qui exécutent Docker et qui font partie d’un cluster. Vous continuez à exécuter les commandes Docker auxquelles vous êtes habitué, mais elles sont maintenant exécutées depuis un nœud manager.

Les managers Swarm peuvent utiliser plusieurs stratégies pour exécuter les containers, telles que le « nœud le plus vide » – qui utilise les machines les moins utilisées, ou « global », qui garantit que chaque nœud obtient exactement une instance du container spécifié.

Vous demandez à l’orchestrateur Swarm d’utiliser ces stratégies dans le fichier compose, tout comme celui que vous avez déjà utilisé auparavant.

Cas d’entreprise

Voici un cas d’entreprise afin d’appuyer les propos qui vont suivre :

Vous êtes consultant en informatique et une entreprise A fait appel à vos compétences afin de remplacer le puits de logs existant.


L’entreprise A n’utilise qu’une machine afin de récolter le Syslog de son routeur.


Le tout est sauvegardé dans un fichier, avec une rotation par jour. La taille de chaque fichier est en moyenne de 3 Go.


L’objectif est de remplacer ce puits de logs en un SIEM

Volumétrie

Afin d’avoir davantage de marge de manœuvre, le volume journalier stocké sur le disque a été fixé à 5Go.

La rétention des données selon la directive 2006/24/CE exige la conservation des données pendant une période allant de six mois à deux ans, en particulier en vue de pouvoir :

  • tracer et identifier la source d’une communication
  • tracer et identifier la destination d’une communication
  • identifier la date, l’heure et la durée d’une communication
  • discerner le type de communication
  • identifier la machine utilisée pour communiquer
  • identifier la location des équipements de communication mobile

La volumétrie annuelle est donc de : 5Go * 365 = 1 825Go de données.

Dimensionnement

Maintenant que la volumétrie est évaluée, il faut assurer une continuité de service. C’est la raison pour laquelle l’ensemble des services ainsi que le matériel seront redondés.

Dans un premier temps, il faut s’assurer de la pérennité des données. La réplication de ces dernières est nécessaire car étant sensibles, l’entreprise ne peut se permettre d’en perdre. C’est pourquoi elles seront dupliquées 2 fois. Le stockage doit être au minimum de : 3 * 1 825 = 5 475Go, soit 5.34To.

Indexes et shards

Par défaut, un index est composé de 5 shards primary et d’1 réplica. Il est courant de voir des shards de taille comprise entre 20Go et 40Go. En reprenant le calcul volumétrique précédent, l’index net-security-syslog-* fera 1.8To de données au bout d’un an.

Avec au maximum 40Go de données par shards, il faudra donc : 1 825Go / 40Go ~= 47 shards. Pour être un peu plus à l’aise, les indexes seront dimensionnés avec des shards de 35Go environ, soit 52 shards sur 1 an pour l’index net-security-syslog-*.

Afin de ne pas avoir un seul index porteur de toutes les données, ce dernier sera divisé en plusieurs sous-indexes, datés par semaine, soit : net-security-syslog-%{+YYYY.ww}, eux-mêmes composés de 5 shards primaires et de 2 réplicas.

Pour terminer, l’infrastructure sera multipliée afin d’assurer une haute disponibilité, une répartition de la charge sur les différents nœuds, mais aussi une meilleure répartition des données afin d’avoir encore la totalité des données si jamais une ou plusieurs machines étaient défaillantes.

Prérequis

Pour ce cas d’entreprise, il faut :

  • 5 machines Ubuntu 18.04.2
  • 1.5 To de stockage / node
  • 16 Go de RAM / node
  • 4 CPUs Intel(R) Core(TM) i5-4460 3.20GHz / node

Installer Docker 18.09.2

Pour commencer, effectuez l’installation de Docker en suivant les instructions sur le site officiel jusqu’à l’installation de Docker CE,Docker CLI et containerd.

$ apt-get install docker-ce=5:18.09.2~3-0~ubuntu-bionic \
                  docker-ce-cli=5:18.09.2~3-0~ubuntu-bionic \
                  containerd.io
$ systemctl enable docker
$ service docker start
$ usermod -aG docker user

Puis déconnectez / reconnectez-vous en tant que user.

Initialisation et configuration de votre cluster Swarm

Pour initialiser Swarm, effectuez les commandes suivantes :

$ docker swarm init --advertise-addr 10.0.0.80
Swarm initialized: current node (9cu80y8oolg4m9sn53kse8a1u) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-6b3o8lh8gfu8qsrxr85iwl7m7tvi82fqzhjr85n0ngmdikb5b0-881i2iesarv7foqyhkpegsevk 10.0.0.80:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Sur chaque hôte du cluster, lancez la commande fournie précédemment par le résultat de l’initialisation du cluster :

$ docker swarm join --token SWMTKN-1-6b3o8lh8gfu8qsrxr85iwl7m7tvi82fqzhjr85n0ngmdikb5b0-881i2iesarv7foqyhkpegsevk 10.0.0.80:2377
This node joined a swarm as a worker.

Ces derniers seront ajoutés en tant que worker par défaut, à la différence de la machine qui initie la création du cluster qui est manager.

$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
visr29j2tb03vp6hxxmywa444 *   swarm1              Ready               Active              Leader              18.09.2
cle7ugskcgre7jbsnn4dwp2my     swarm2              Ready               Active                                  18.09.2
oe0coaikgm0h7eciv54daelzu     swarm3              Ready               Active                                  18.09.2
uzbalyqmguhhaf7dh1yzkkyy2     swarm4              Ready               Active                                  18.09.2
9jhcdfyu4e99j0ddndmtmbou0     swarm5              Ready               Active                                  18.09.2

Ajouter des nœuds manager pour la tolérance aux pannes du cluster Swarm

Tout comme le cluster Elasticsearch, Swarm a besoin de plusieurs nœuds de type manager afin d’assurer : l’état du cluster, les services de planification, etc.

De plus, il est nécessaire d’avoir une tolérance aux pannes, comme le cluster Elasticsearch. Il est important de conserver un nombre impair de nœuds manager. Cela garantit que le quorum reste disponible pour traiter les demandes si le réseau est partitionné en deux ensembles.

https://docs.docker.com/engine/swarm/admin_guide/

Pour promouvoir des nœuds du cluster Swarm en manager, passez la commande suivante :

$ docker node promote [node_id]

Dans ce cas :

$ docker node promote cle7ugskcgre7jbsnn4dwp2my
$ docker node promote oe0coaikgm0h7eciv54daelzu
$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
visr29j2tb03vp6hxxmywa444 *   swarm1              Ready               Active              Reachable           18.09.2
cle7ugskcgre7jbsnn4dwp2my     swarm2              Ready               Active              Reachable           18.09.2
oe0coaikgm0h7eciv54daelzu     swarm3              Ready               Active              Leader              18.09.2
uzbalyqmguhhaf7dh1yzkkyy2     swarm4              Ready               Active                                  18.09.2
9jhcdfyu4e99j0ddndmtmbou0     swarm5              Ready               Active                                  18.09.2

Maintenant que les nœuds sont déployés, il serait intéressant de pouvoir les identifier afin d’apposer des contraintes lors des déploiements.

Par exemple, les containers ELK de type master pourraient être déployés sur des nœuds spécifiques, les containers de type data sur d’autres, etc.

Dans ce cas, les contraintes de déploiements suivantes :

  • Déployer les containers Elasticsearch de type master sur des nœuds spécifiques

Pour ce faire, il faut ajouter un label sur les nœuds Swarm concernés depuis un nœud manager de la façon suivante :

$ docker node update --label-add elk=master visr29j2tb03vp6hxxmywa444
$ docker node update --label-add elk=master cle7ugskcgre7jbsnn4dwp2my
$ docker node update --label-add elk=master oe0coaikgm0h7eciv54daelzu

Pour lister les nœuds avec les labels :

$ docker node ls -q | xargs docker node inspect \
  -f '{{ .ID }} [{{ .Description.Hostname }}]: {{ .Spec.Labels }}'
visr29j2tb03vp6hxxmywa444 [swarm1]: map[elk:master]
cle7ugskcgre7jbsnn4dwp2my [swarm2]: map[elk:master]
oe0coaikgm0h7eciv54daelzu [swarm3]: map[elk:master]
uzbalyqmguhhaf7dh1yzkkyy2 [swarm4]: map[]
9jhcdfyu4e99j0ddndmtmbou0 [swarm5]: map[]

Afin d’avoir une vue du cluster Swarm, les containers qui y sont déployés, il est intéressant d’utiliser le service Visualizer.

Depuis un nœud manager :

docker run -it -d \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
dockersamples/visualizer:stable

Une fois déployé, il est possible d’accéder à Visualizer depuis l’IP du nœud sur le port 8080.

https://docs.docker.com/engine/swarm/ingress/

Vue du cluster Swarm avec Visualizer

Déploiement de la Stack ELK

Prérequis système

Maintenant que Swarm est prêt, il faut remplir certains prérequis système avant de pouvoir déployer la Stack ELK.

En effet, il est nécessaire de désactiver la mémoire SWAP, augmenter le nombre maximal de descripteurs de fichiers ouverts, augmenter le nombre de mmap ou encore augmenter le nombre de connexions qu’Elasticsearch peut demander.

Pour ce faire, éditez le fichier /etc/sysctl.conf et ajoutez les lignes suivantes :

$ echo /etc/sysctl.conf
vm.swappiness=1
net.core.somaxconn=65535
vm.max_map_count=262144
fs.file-max=518144

De plus, le démon Docker est souvent démarré avec une limite trop basse pour la mémoire verrouillable. Les containers héritent de cette limite, ce qui pose un problème pour Elasticsearch lorsqu’il tente de verrouiller plus de mémoire que celle autorisée.

Pour regarder les limites du démon Docker :

$ grep locked /proc/$(ps --no-headers -o pid -C dockerd | tr -d ' ')/limits
Max locked memory         65536                65536                bytes

Alors que dans ce cas il serait préférable de voir unlimited. Pour ce faire :

$ echo -e "[Service]\nLimitMEMLOCK=infinity" | SYSTEMD_EDITOR=tee systemctl edit docker.service
systemctl daemon-reload
systemctl restart docker

Vérification :

$ grep locked /proc/$(ps --no-headers -o pid -C dockerd | tr -d ' ')/limits
Max locked memory         unlimited            unlimited            bytes

Vous trouverez toutes les informations nécessaires en suivant ce lien

Explication

La Stack est composée de plusieurs services, définis dans un fichier docker-compose.yml. Il est courant d’utiliser ce format de fichier dans le cadre d’un déploiement de containers, mais de nouvelles fonctionnalités sont ajoutées pour l’orchestrateur Swarm.

Voici le docker-compose final du déploiement :

version: "3.7"

services:
################################################################################
################################## CONFIGURATION ###############################
################################################################################
  visualizer:
    image: dockersamples/visualizer:stable
    networks:
      - net
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      labels:
        - "traefik.port=8080"
        - "traefik.backend=visualizer"
        - "traefik.frontend.rule=Host:visualizer-elk.net-security.fr"
        - "traefik.frontend.auth.basic=admin:$$2y$$05$$vHovNtz4FPZx49eK0JeGoenXLA4D/5h0i5QoS50L90GN3OlfFkjW."
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          memory: 256M
        reservations:
          memory: 128M

  traefik:
    image: registry.gitlab.com/net-security/elk/traefik:1.7
    ports:
      - "80:80"
      - "443:443"
    networks:
      - net
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: 'global'
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          memory: 256M
        reservations:
          memory: 128M
################################################################################
################################### ELK STACK ##################################
################################################################################
  elastic-master:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.7.0
    environment:
      discovery.zen.minimum_master_nodes: 2
      discovery.zen.ping.unicast.hosts: elastic-master
      discovery.zen.ping_timeout: 5s
      discovery.zen.commit_timeout: 5s
      node.master: "true"
      node.data: "false"
      node.ingest: "false"
      cluster.remote.connect: "false"
      cluster.name: docker-swarm-cluster
      network.host: 0.0.0.0
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    networks:
      - net
    deploy:
      endpoint_mode: dnsrr
      mode: 'replicated'
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      placement:
        constraints: [node.labels.elk == master]
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

  elastic-data-1:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.7.0
    environment:
      discovery.zen.minimum_master_nodes: 2
      discovery.zen.ping.unicast.hosts: elastic-master
      discovery.zen.ping_timeout: 5s
      discovery.zen.commit_timeout: 5s
      node.master: "false"
      node.data: "true"
      node.ingest: "false"
      cluster.remote.connect: "false"
      cluster.name: docker-swarm-cluster
      network.host: 0.0.0.0
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    networks:
      - net
    volumes:
      - esdata1:/usr/share/elasticsearch/data
    deploy:
      placement:
       constraints: [node.hostname == swarm1]
      mode: 'replicated'
      replicas: 1
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

  elastic-data-2:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.7.0
    environment:
      discovery.zen.minimum_master_nodes: 2
      discovery.zen.ping.unicast.hosts: elastic-master
      discovery.zen.ping_timeout: 5s
      discovery.zen.commit_timeout: 5s
      node.master: "false"
      node.data: "true"
      node.ingest: "false"
      cluster.remote.connect: "false"
      cluster.name: docker-swarm-cluster
      network.host: 0.0.0.0
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    networks:
      - net
    volumes:
      - esdata2:/usr/share/elasticsearch/data
    deploy:
      placement:
        constraints: [node.hostname ==swarm2]
      mode: 'replicated'
      replicas: 1
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

  elastic-data-3:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.7.0
    environment:
      discovery.zen.minimum_master_nodes: 2
      discovery.zen.ping.unicast.hosts: elastic-master
      discovery.zen.ping_timeout: 5s
      discovery.zen.commit_timeout: 5s
      node.master: "false"
      node.data: "true"
      node.ingest: "false"
      cluster.remote.connect: "false"
      cluster.name: docker-swarm-cluster
      network.host: 0.0.0.0
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    networks:
      - net
    volumes:
      - esdata3:/usr/share/elasticsearch/data
    deploy:
      placement:
        constraints: [node.hostname ==swarm3]
      mode: 'replicated'
      replicas: 1
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

  elastic-data-4:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.7.0
    environment:
      discovery.zen.minimum_master_nodes: 2
      discovery.zen.ping.unicast.hosts: elastic-master
      discovery.zen.ping_timeout: 5s
      discovery.zen.commit_timeout: 5s
      node.master: "false"
      node.data: "true"
      node.ingest: "false"
      cluster.remote.connect: "false"
      cluster.name: docker-swarm-cluster
      network.host: 0.0.0.0
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    networks:
       - net
    volumes:
      - esdata4:/usr/share/elasticsearch/data
    deploy:
      placement:
        constraints: [node.hostname ==swarm4]
      endpoint_mode: dnsrr
      mode: 'replicated'
      replicas: 1
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

  elastic-data-5:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.7.0
    environment:
      discovery.zen.minimum_master_nodes: 2
      discovery.zen.ping.unicast.hosts: elastic-master
      discovery.zen.ping_timeout: 5s
      discovery.zen.commit_timeout: 5s
      node.master: "false"
      node.data: "true"
      node.ingest: "false"
      cluster.remote.connect: "false"
      cluster.name: docker-swarm-cluster
      network.host: 0.0.0.0
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    networks:
       - net
    volumes:
      - esdata5:/usr/share/elasticsearch/data
    deploy:
      placement:
        constraints: [node.hostname ==swarm5]
      endpoint_mode: dnsrr
      mode: 'replicated'
      replicas: 1
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

  elastic-coordination:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.7.0
    environment:
      discovery.zen.minimum_master_nodes: 2
      discovery.zen.ping.unicast.hosts: elastic-master
      discovery.zen.ping_timeout: 5s
      discovery.zen.commit_timeout: 5s
      node.master: "false"
      node.data: "false"
      node.ingest: "true"
      cluster.remote.connect: "false"
      cluster.name: docker-swarm-cluster
      network.host: 0.0.0.0
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    networks:
      - net
    deploy:
      endpoint_mode: dnsrr
      mode: 'global'
      update_config:
        parallelism: 2
        delay: 10s
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G

  kibana:
    image: docker.elastic.co/kibana/kibana:6.7.0
    environment:
      ELASTICSEARCH_HOSTS: http://elastic-coordination:9200
    networks:
      - net
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.port=5601"
        - "traefik.backend=kibana"
        - "traefik.frontend.rule=Host:kibana-elk.net-security.fr"
        - "traefik.frontend.auth.basic=admin:$$2y$$05$$vHovNtz4FPZx49eK0JeGoenXLA4D/5h0i5QoS50L90GN3OlfFkjW."
      mode: replicated
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

  logstash-syslog:
    image: registry.gitlab.com/net-security/elk/logstash-syslog:latest
    ports:
      - "5000:5000/udp"
    networks:
      - net
    deploy:
      mode: global
      update_config:
        parallelism: 2
        delay: 10s
      resources:
        limits:
          memory: 4G
        reservations:
          memory: 2G


################################################################################
################################# CONFIGURATION ################################
################################################################################
networks:
  net:
    driver: overlay

volumes:
    esdata1:
    esdata2:
    esdata3:
    esdata4:
    esdata5:

Afin d’assurer une haute disponibilité des services, il est judicieux de redonder l’ensemble des services.

Des contraintes d’emplacement sont spécifiés pour certains services, ainsi que des modes de déploiement différents, comme le mode global ou le mode replicated.

En mode global, Docker Swarm va s’assurer que chaque nœud du cluster possède une instance du service, alors qu’en mode replicated, Swarm déploiera autant d’instances de ce service que précisé.

Traefik

Traefik est un reverse proxy HTTP et un équilibreur de charge moderne qui facilite le déploiement de micro services. Il sera utilisé en tête des services afin qu’il redirige les requêtes HTTP des frontends vers les backends associés.

Le mode de déploiement est en mode replicated, au nombre de 3.

Ici l’image Docker provient d’un registry privé Gitlab car c’est une image personnalisée. Un certificat SSL ainsi que le fichier de configuration sont ajoutés à cette dernière, qui sera construite et déployée automatiquement grâce à Gitlab CI/CD.

Le fichier traefik.toml est le suivant :

################################################################
# Docker Provider
################################################################
defaultEntryPoints = ["http", "https"]
    insecureSkipVerify = true
    [entryPoints]
      [entryPoints.http]
        address = ":80"
        [entryPoints.http.redirect]
          entryPoint = "https"
      [entryPoints.https]
        address = ":443"
        [entryPoints.https.tls]
            [[entryPoints.https.tls.certificates]]
            CertFile = "/ssl/fullchain1.pem"
            KeyFile = "/ssl/privkey1.pem"

# Enable Docker Provider.
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "net-security.fr"
watch = true
exposedByDefault = true
usebindportip = true
swarmMode = true
network = "net"

[api]
  entryPoint = "traefik"
  dashboard = true
  debug = true

Afin de limiter les accès à certains services tels que Visualizer ou Kibana, il faut dans un premier temps utiliser l’Authentification Basic que propose Traefik. Il suffit de générer un couple user:password de la façon suivante :

$ echo $(htpasswd -nbB admin "admin") | sed -e s/\\$/\\$\\$/g
admin:$$2y$$05$$vHovNtz4FPZx49eK0JeGoenXLA4D/5h0i5QoS50L90GN3OlfFkjW.

Pour plus d’informations concernant les différentes possibilités qu’offre Traefik, rendez-vous ici.

Kibana

Le déploiement du service Kibana est en mode replicated, avec un nombre d’instances égal à 2. Le nœud sur lequel sera déployé Kibana a peu d’importance, l’idée est simplement de s’assurer que ce service soit toujours disponible.

Afin d’avoir la main sur les ressources du cluster et du nœud, les ressources telles que la RAM sont limitées mais aussi réservées.

Enfin, l’ajout de la variable d’environnement JAVA ELASTICSEARCH_HOSTS: http://elastic-coordination:9200 permet aux requêtes envoyées à Kibana de passer par un type de nœud Elasticsearch spécifique, les nœuds de type coordinating qui seront présentées par la suite.

Attention, il faut que le nom DNS du nœud coordinating soit identique au nom du service qui se trouve dans le docker-compose.yml

En utilisant le nom du service et non l’IP d’un nœud du cluster au sein de Swarm, la haute disponibilité du service de routing et de load balancer d’Elasticsearch est assurée, ce grâce au mode de routing mesh de Swarm.

Logstash

Logstash est le service déployé afin de traiter le flux Syslog du routeur de l’entreprise, en deux parties :

  • la partie input, qui spécifie le port d’écoute, le type d’input, et le protocole, ici UDP
  • la partie output, qui redirige le flux vers les services Elasticsearch de type coordinating ainsi que l’index associé. Là l’index est de type année-numerodesemaine, comme vu précédemment.
input {
    udp {
        port => 5000
        type => syslog
    }
}

output {
    elasticsearch {
        hosts => ["elastic-coordination:9200"]
        index => "net-security-syslog-%{+YYYY.ww}"
    }
}

Elasticsearch

Le cluster Elasticsearch doit avoir plusieurs nœuds maîtres afin d’éviter les problèmes de cerveau divisé, ou split-brain.

Le problème de split-brain se produit lorsque deux parties du cluster ne peuvent pas communiquer et pensent que l’autre partie est hors-service. Ce problème risquerait de faire perdre des données parce que le maître du cluster décide quand de nouveaux indices peuvent être créés, comment les shards sont déplacés, etc.

S’il y a 2 maîtres, l’intégrité des données devient périlleuse parce qu’il y aura 2 nœuds qui pensent être master du cluster, mais chacun de leur côté

Pour éviter la situation du split-brain, il faut utiliser le paramètre suivant :

discovery.zen.minimum_master_nodes: 2

Ce paramètre indique à Elasticsearch de ne pas élire un maître s’il n’y a pas assez de nœuds maîtres disponibles. Ce n’est qu’à ce moment-là qu’il y aura des élections

Le quorum est calculé de la façon suivante : (nombre de nœuds éligibles au master / 2) + 1.

Dans ce cas, le cluster est composé de 3 nœuds maîtres dédiés et 5 nœuds de données, le quorum est de 2, puisqu’il est nécessaire de seulement compter les nœuds qui sont maîtres éligibles.

https://docs.docker.com/engine/swarm/admin_guide/

Elasticsearch est configuré pour utiliser la découverte multicast prête à l’emploi. La multidiffusion fonctionne en envoyant des pings UDP sur votre réseau local pour découvrir les nœuds. Les autres nœuds Elasticsearch recevront ces pings et répondront. Un cluster est formé peu de temps après.

Le paramètre à appliquer est :

discovery.zen.ping.unicast.hosts: elastic-master

Pour terminer, chaque container doit avoir sa propre fonction au sein du cluster ELK.

Il existe plusieurs type de nœud comme les nœuds master, data, coordinating ou encore ingest. Pour spécifier le type de nœud au container, il faut passer les variables suivantes, en ne mettant la valeur true qu’à un paramètre sur les 3.

node.master: "true"
node.data: "false"
node.ingest: "false"

Un nœud de type coordinating n’est aucun des trois types précédents, il faut donc tout passer à false.

Pour plus d’informations sur les différents types de nœuds, c’est par .

Déploiement

Maintenant que tous les containers sont paramétrés, il ne reste plus qu’à déployer la Stack à l’aide de la commande suivante :

$ docker login registry.gitlab.com -u user -p password
$ docker stack deploy --compose-file docker-compose.yml \
elk-net-security --with-registry-auth

Comme dit précédemment, la création d’images est automatisée grâce à Gitlab CI/CD et leur stockage est effectué au sein d’un registry privé. C’est la raison pour laquelle il faut utiliserdocker login ainsi que le paramètre –with-registry-auth.

Pour vérifier que la Stack existe :

$ docker stack ls
NAME                SERVICES            ORCHESTRATOR
elk-net-security    11                  Swarm

Afin de vérifier que les différents services s’exécutent et que le nombre de réplicas souhaité est respecté :

$ docker service ls
ID                  NAME                                    MODE                REPLICAS            IMAGE                                                        PORTS
jl8igvhv3dw6        elk-net-security_elastic-coordination   global              5/5                 docker.elastic.co/elasticsearch/elasticsearch:6.7.0
td8ywoyldvzk        elk-net-security_elastic-data-1         replicated          1/1                 docker.elastic.co/elasticsearch/elasticsearch:6.7.0
ytaqblikt2yj        elk-net-security_elastic-data-2         replicated          1/1                 docker.elastic.co/elasticsearch/elasticsearch:6.7.0
ung0ymqvp69j        elk-net-security_elastic-data-3         replicated          1/1                 docker.elastic.co/elasticsearch/elasticsearch:6.7.0
piumng277siw        elk-net-security_elastic-data-4         replicated          1/1                 docker.elastic.co/elasticsearch/elasticsearch:6.7.0
ixmyision5og        elk-net-security_elastic-data-5         replicated          1/1                 docker.elastic.co/elasticsearch/elasticsearch:6.7.0
tldg967htz62        elk-net-security_elastic-master         replicated          3/3                 docker.elastic.co/elasticsearch/elasticsearch:6.7.0
k7mo9wvzmnka        elk-net-security_kibana                 replicated          2/2                 docker.elastic.co/kibana/kibana:6.7.0
afcshnqdcipb        elk-net-security_logstash-syslog        global              5/5                 registry.gitlab.com/net-security/elk/logstash-syslog:latest  *:5000->5000/udp
oekhg4esqfkj        elk-net-security_traefik                global              3/3                 registry.gitlab.com/net-security/elk/traefik:latest          *:80->80/tcp, *:443->443/tcp
ov0kcwiplm73        elk-net-security_visualizer             replicated          1/1                 dockersamples/visualizer:stable

Comme vu plus haut, il est possible de voir l’ensemble des containers déployés au sein du cluster. Rendez-vous sur https://visualizer-elk.net-security.fr.

Saisissez admin:admin et vous voilà authentifié, avec l’accès à la vue de Visualizer.

Vue du cluster Swarm avec Visualizer

Parfait ! Maintenant, rendez-vous sur
https://kibana-elk.net-security.fr, saisissez admin:admin. Voici les vues que vous devriez avoir :

Page de lancement de Kibana
Page d’accueil de Kibana

Suite au déploiement de la Stack, il est nécessaire de mettre en place le nombre de réplicas pour les indexes avant de rediriger le flux Syslog vers Logstash.

La modification du nombre de réplicas d’un index ne peut se faire qu’avant la création de ce dernier.

Pour ce faire, il faut créer un template et pousser le fichier de configuration suivant sur n’importe quel nœud du cluster Elasticsearch (au sein d’un container) :

{
  "order": 0,
  "index_patterns": "net-security-syslog-*",
  "settings": {
    "index": {
      "refresh_interval": "5s",
      "number_of_shards": "5",
      "number_of_replicas": "2"
    }
  },
  "mappings": {
    "doc": {
      "properties": {
        "@timestamp": {
          "type": "date"
        },
        "@version": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "host": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "message": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "type": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
      }
    }
  }
}

Afin de le déployer, voici la commande à lancer :

curl -XPUT 'http://localhost:9200/_template/net-security-syslog' \
-H 'Content-Type: application/json' \
-d @net-security-syslog.json

Pour terminer, il ne reste plus qu’à rediriger le flux de l’ancien puits de logs vers le SIEM ELK, sur n’importe quel nœud du cluster vers le port 5000.

Haute disponibilité avec keepalived

Le cluster dispose de services hautement disponibles, repartis sur 5 nœuds.

Si deux nœuds de type data sont corrompus, l’intégrité reste assurée. Si deux nœuds de type master sont défaillants, l’ensemble du cluster est encore fonctionnel. Il en est de même avec Logstash et Kibana.

Mais qu’arriverait-il si jamais le nœud qui reçoit la sortie Syslog du routeur de l’entreprise est défaillant ? Il y a ici un SPOF (Single Point Of Failure) qui met à plat la Stack hautement disponible.

Keepalived est une solution (libre et gratuite) « tout en un » de haute disponibilité, de virtualisation de services applicatifs et de répartition de charge. Elle s’appuie entièrement sur des fonctionnalités du noyau Linux et des protocoles standardisés tout en consolidant ces technologies dans une même solution. Keepalived est un outil extrêmement léger, très facile à appréhender, à configurer et à déployer.

Pour continuer sur la lancée de dockerisation, Keepalived sera déployé dans un container, sur chaque hôte du cluster, mais pas au sein de la Stack d’ELK.

Le container est à déployer de la façon suivante :

docker run -d --name keepalived --restart=always \
--cap-add=NET_ADMIN --net=host \
-e KEEPALIVED_UNICAST_PEERS="#PYTHON2BASH:['10.0.0.80', '10.0.0.81', '10.0.0.82', '10.0.0.83', '10.0.0.84']" \
-e KEEPALIVED_VIRTUAL_IPS=10.0.0.89 \
-e KEEPALIVED_PRIORITY=12 \
-e KEEPALIVED_INTERFACE=enp3s0 \
osixia/keepalived:2.0.15

Attention, bien penser à positionner une priorité différente pour chaque nœud du cluster.

Pour vérifier que l’adresse IP virtuelle VRRP fonctionne sur le bon hôte, voici la commande à lancer sur les hôtes du cluster :

$  ip addr show enp3s0
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 2c:56:dc:d4:8a:36 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.84/25 brd 10.13.9.127 scope global enp3s0
       valid_lft forever preferred_lft forever
    inet 10.0.0.89/32 scope global enp3s0
       valid_lft forever preferred_lft forever
    inet6 fe80::2e56:dcff:fed4:8a36/64 scope link
       valid_lft forever preferred_lft forever

Une fois la vérification faite, il faut modifier l’IP cible du flux Syslog de routeur pour maintenant la faire pointer sur la VIP.

Le cluster est désormais hautement disponible 🙂

Les points à améliorer

La Stack maintenant déployée, il reste certains points à améliorer et à mettre en place, comme :

  • Déployer une Stack de monitoring (Prometheus, Grafana etc.)
  • Gérer la CPU des containers comme la RAM
  • Mettre en place le stockage à chaud et à froid
  • Gérer la rotation des logs (1 an)
  • Configurer les Persistent Queues de Logstash

Je vous parlerai de ces points dans un prochain article.

D’ici là, j’espère que cet article vous aura plu, si vous avez des questions ou des remarques sur ce que j’ai pu écrire n’hésitez pas à réagir avec moi par mail ou en commentaire !

Merci pour votre lecture et à bientôt !

APavone


7 commentaires

alex · 7 mai 2019 à 10 h 11 min

Bienvenu dans le monde des usines à gaz !!!

    Adrien Pavone · 7 mai 2019 à 10 h 28 min

    Bonjour,
    il est vrai que de loin ça à l’air d’être une usine à gaz. L’article est particulièrement long, et la mise en place paraît fastidieuse.
    Cependant, que ce soit sous Docker ou non, ELK ou un autre SIEM, je pense que si nous voulons une solution résiliente et performante il y a des études à mener et du coup une mise en place à faire.
    Je pense même que grâce à Docker Swarm c’est beaucoup plus simple à maintenir, surtout au niveau des migrations. Un coup de docker service update et « c’est fini » (voir parallelism).
    Edit: Il ne faut pas oublier que cette solution est open source et gratuite, et que seul le support est payant, ce qui n’est pas négligeable (25 000 euros / an pour 5 nœuds de type data)

Benjamin · 24 juillet 2019 à 11 h 18 min

Super article qui permet de s’epargner beaucoup de difficultes !
Merci beaucoup !!

Patrick · 18 octobre 2019 à 10 h 17 min

Merci beaucoup !!
Superbe article , très efficace 🙂
Il m’aurait fait gagné 2 jours si je l’avais trouvé (2 jours avant) 😀 😀

Hugo · 2 juin 2020 à 11 h 55 min

Bonjour,

Ce tutoriel est tres clair et bien detaille, malheureusement lors du deploiement les services ont des erreurs, les images ne sont pas trouvees : « No such image: registry.gitlab.com/net-security/elk/traefik:1.7 » en erreur quand je regarde mon service traefik apres deploiement. J’imagine qu’il suffit de modifier des chemins, mais du coup que mettre a la place?

    Adrien Pavone · 4 juin 2020 à 0 h 04 min

    Salut Hugo, je te remercie pour ton commentaire !
    Concernant ton problème, cette image vient de la registry docker privée de mon projet sur Gitlab. Effectivement, je construis l’image Docker automatiquement à chaque fois que je modifie le fichier traefik.toml par exemple, puisque j’en fait une image personnalisée, avec cette configuration particulière.
    La solution pour ton problème serait d’avoir une registry privée (registry de Docker, Gitlab, harbor etc.) où tu pousserais ton image personnalisée, pour la récupérer par la suite, comme ici OU ALORS, tu peux passer ton fichier configuration en tant que commandes et arguments lors du lancement de ton image. Tu pourrais de même gérer automatiquement la génération de ton certificat avec Let’s Encrypt.
    Un exemple de cette configuration serait :

    traefik:
    image: traefik:v2.1.3
    networks:
    - net
    environment:
    TZ: Europe/Paris
    command:
    - "--providers.docker=true"
    - "--providers.docker.exposedbydefault=false"
    - "--entrypoints.web.address=:80"
    - "--entrypoints.web-secure.address=:443"
    - "--certificatesresolvers.apptlschallenge.acme.tlschallenge=true"
    - "--certificatesresolvers.apptlschallenge.acme.email=adrien.pavone@net-security.fr"
    - "--certificatesresolvers.apptlschallenge.acme.storage=/letsencrypt/acme.json"
    - "--certificatesresolvers.appmetricstlschallenge.acme.tlschallenge=true"
    - "--certificatesresolvers.appmetricstlschallenge.acme.email=adrien.pavone@net-security.fr"
    - "--certificatesresolvers.appmetricstlschallenge.acme.storage=/letsencrypt/acme.json"
    ports:
    - 80:80
    - 443:443
    volumes:
    - "/var/run/docker.sock:/var/run/docker.sock:ro"
    - "traefik_cert:/letsencrypt"
    deploy:
    mode: 'global'
    placement:
    constraints:
    - node.role == manager
    resources:
    limits:
    memory: 256M
    reservations:
    memory: 128M

    kibana:
    image: docker.elastic.co/kibana/kibana:6.7.0
    environment:
    ELASTICSEARCH_HOSTS: http://elastic-coordination:9200
    networks:
    - net
    deploy:
    labels:
    - "traefik.enable=true"
    - "traefik.http.middlewares.app-redirect-web-secure.redirectscheme.scheme=https"
    - "traefik.http.routers.app.middlewares=app-redirect-web-secure"
    - "traefik.http.routers.app.entrypoints=web"
    - "traefik.http.routers.app.rule=host(`kibana.net-security.fr`)"
    - "traefik.http.routers.app-secure.entrypoints=web-secure"
    - "traefik.http.routers.app-secure.rule=host(`kibana.net-security.fr`)"
    - "traefik.http.routers.app-secure.tls.certresolver=apptlschallenge"
    - "traefik.http.routers.app-secure.tls=true"
    - "traefik.http.services.app-secure.loadbalancer.server.port=5601"
    - "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$05$$vHovNtz4FPZx49eK0JeGoenXLA4D/5h0i5QoS50L90GN3OlfFkjW."
    mode: replicated
    replicas: 2
    update_config:
    parallelism: 1
    delay: 10s
    resources:
    limits:
    memory: 512M
    reservations:
    memory: 256M

    Normalement cette solution devrait solutionner ton problème.

Déploiement d'une stack ELK en mode Swarm - My Tiny Tools · 6 mai 2019 à 14 h 56 min

[…] (Source: Journal du hacker) […]

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.