Il est maintenant indéniable qu’Hashicorp est une entreprise qui révolutionne l’univers de l’IAC. Entre Terraform qui est adopté par la plupart des clouds providers, Packer et Vagrant pour produire des machines ou des environnements de développement, ou encore Nomad comme alternative à Kubernetes : Hashicorp a su s’imposer via des outils fiables et efficaces.

J’oriente mes sujets d’apprentissages en fonction des tendances du moment et de mes besoins professionnels, et comme la principale évolution de la décennie (dans l’univers du développement) est la gestion des micro-services, je me suis orienté vers Kubernetes qui propose un environnement très favorable à cette pratique.

Mais Kubernetes n’est pas libre de toute contrainte, et je peux comprendre que certains milieux ne soient pas prêts à passer le cap.

C’est pour proposer une alternative à Kubernetes-Istio que j’ai commencé à apprendre Consul.

Je vais alors vous parler de Consul de A à Y, car je ne suis pas encore arrivé à la lettre Z (et je ne pense pas que j’y arriverai un jour).

Qu’est ce que Consul ?

Consul est l’outil développé par Hashicorp dans sa catégorie “Réseau”, mais ne vous découragez pas : aucune notion de réseau n’est nécéssaire à son utilisation.

En simplifiant à l’extrême : Consul permet d’interconnecter des services.

Celui-ci dispose de 2 modes de fonctionnement : agent et serveurs.

Les agents doivent identifier les services présents sur une machine, et les ajouter au catalogue de service. Les serveurs doivent orienter les requêtes d’un service à un autre.

Par exemple, je dispose de 2 micro-services : un service A qui propose un dashboard dont les informations sont récupérées depuis le service B, et le service B qui propose un API REST pour récupérer ces informations.

En temps normal : il est nécéssaire d’indiquer l’IP:PORT du service B au service A. Mais qu’en est-il du cas où nous voulons redonder le service B ? Quelle IP devrons nous donner au dashboard ? Il serait nécéssaire d’installer un HAProxy (ou autre) qui devrait rediriger en s’assurant quelles machines sont fonctionnelles ou pas. Et les HealthChecks de HAProxy ne sont pas forcement très permissifs : HAProxy ne peut pas voir l’état de santé complet de la machine…

(À noter qu’il est possible de bricoler HAProxy pour qu’il utilise le catalogue de Consul. Je laisserai un lien pour intégrer Consul et HAProxy en bas de l’article)

Sur Kubernetes, un “service” pointe forcément vers un Pod dont le liveness/readiness montre qu’il est en bonne santé (parmis une liste de pods de la même application). Avec Consul : l’agent installé va vérifier si le micro-service et le système sont fonctionnels et si les conditions sont bonnes, l’agent fait remonter que le service est disponible et le serveur va rediriger le service A vers la bonne machine.

Pour rediriger le trafic, Consul peut le faire de 3 manières différentes:

  • par DNS - Consul héberge un serveur DNS redirigeant vers une IP pointant vers le micro-service demandé.
  • par API/SDK - L’application doit faire une requête en REST pour obtenir l’IP du micro-service.
  • par un sidecar proxy - Créant un tunnel dont le flux et l’accès sont gérés par Consul (qui peut autoriser ou non la communication entre les services).

Lorsqu’on passe par le DNS ou le sidecar, l’application n’a pas conscience de Consul.

Prenons le cas où nous passons par DNS :

Schéma simple Consul-DNS

  1. Le service A demande l’IP du domaine service-b.service.consul (Consul va directement comprendre quel service est concerné).
  2. Consul va vérifier si le service-b est fonctionnel (application/système).
  3. L’agent remonte le status de Consul.
  4. Consul donne l’IP du service-B.
  5. Le service A communique avec le B.

Information

Petite précision : Consul ne va pas vérifier en temps réel que le service B est en bonne santé. Il va constamment actualiser le statut des services, donc à la moindre requête : il sait d’avance vers quelle machine rediriger.

C’est l’usage le plus simple de Consul, mais ne vous inquietez pas : nous n’allons pas nous arreter à ça ! Il manque encore de la haute-disponibilité, des règles pour autoriser (ou pas) les services à communiquer et une bonne dose de sécurité.

Mais avant d’en arriver là : il va falloir créer notre environnement de travail.

Créer notre cluster Consul

Je n’ai parlé que de serveur Consul, mais il est possible de faire fonctionner Consul en cluster. Pour assurer un minimum de haute-dispo, il nous faudra minimum 3 noeuds :

Nombre de serveurTaille du QuorumPerte de serveur tolérée
110
220
321
431
532
642
743

Installons alors nos 3 noeuds.

NomIPDescription
consul-server-01192.168.1.151Machine 01 du cluster
consul-server-02192.168.1.152Machine 02 du cluster
consul-server-03192.168.1.153Machine 03 du cluster

Consul n’a presque aucune dépendance et est sous la forme d’un simple binaire.

Je crée 3 machines sous AlpineOS et on passe direct à l’installation.

Consul est également disponible sur la majorité des distributions Linux, voici la procédure pour l’installer sur une machine Debian.

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install consul

Dans mon cas, j’utilise Alpine (comme précisé ci-dessus), voici comment j’installe Consul sur une machine Alpine:

apk add consul
mkdir -p /etc/consul.d/
rm -r /etc/consul/
mkdir -p /opt/consul
chown consul:consul /etc/consul.d/ /opt/consul 
echo 'consul_opts="agent -config-dir=/etc/consul.d"' > /etc/conf.d/consul

Le package Alpine est un peu vieux, alors avec ces commandes : je place les fichiers de configuration dans /etc/consul.d/ et le dossier pour la persistence sera dans /opt/consul (ce qui correspond à la documentation actuelle de Consul).

Créons maintenant la configuration de notre premier noeud :

datacenter = "coffee"
data_dir = "/opt/consul"
client_addr = "0.0.0.0"
ui_config{
  enabled = true
}
connect {
  enabled = true
}
server = true
bind_addr = "0.0.0.0"
bootstrap_expect=1
advertise_addr = "192.168.1.151" # à changer pour mettre l'IP de la machine
retry_join = ["192.168.1.151", "192.168.1.152", "192.168.1.153"]

Avertissement

Vous devrez adapter l’IP en fonction de votre noeud.

Ce fichier va créer notre premier noeud qui va lui-même créer le datacenter “coffee”. Il est possible de gérer plusieurs zones géographiques avec Consul, mais nous n’allons pas nous en occuper dans cet article (peut-être dans un prochain).

En utilisant ce fichier:

  • Vous déclarez que le noeud est un type “serveur”.
  • Vous activez l’interface WEB sur ce noeud (l’UI).
  • Vous autorisez l’utilisation d’un Mesh proxy ‘Consul Connect’ (nous verrons ça plus tard).
  • Vous rejoignez les clusters des noeuds suivants : “192.168.1.151”, “192.168.1.152” et “192.168.1.153”.

Peuplons ensuite les configurations des autres noeuds et en allant sur l’interface web, nous devrions avoir une “Server fault tolerance” à 1.

Information

Si l’UI est activée, l’interface est disponible sur le port 8500 en http.

Fault Tolerance

Nous pouvons aussi voir les machines présentes dans le cluster via la commande consul members :

# consul members
Node              Address             Status  Type    Build   Protocol  DC      Partition  Segment
consul-server-01  192.168.1.151:8301  alive   server  1.16.0  2         coffee  default    <all>
consul-server-02  192.168.1.152:8301  alive   server  1.15.4  2         coffee  default    <all>
consul-server-03  192.168.1.153:8301  alive   server  1.15.4  2         coffee  default    <all>

Ajouter un client au cluster

Il existe 2 principales méthodes pour joindre une machine à un cluster :

  • Via le fichier de configuration.
  • Via la CLI.

La méthode que nous allons toujours privilégier est celle du fichier de configuration.

J’installe alors une nouvelle machine Alpine via la même procédure que pour les serveurs et je place le fichier de configuration suivant :

datacenter = "coffee"
data_dir = "/opt/consul"
client_addr = "0.0.0.0"
server = false
bind_addr = "0.0.0.0"
advertise_addr = "192.168.1.120" # IP de la machine cliente
retry_join = ["192.168.1.151", "192.168.1.152", "192.168.1.153"]

Après avoir démarré le service Consul, nous pouvons voir que la machine est bien présente dans le registre :

Nouveau membre

Cette machine accueillera un micro-service ne servant qu’à compter (je l’ai donc nommé “counting”).

Une première chose sympatique à tester est de récupérer l’IP grâce au nom de la machine :

# dig @192.168.1.151 -p 8600 counting.node.consul ANY

; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> @192.168.1.151 -p 8600 counting.node.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14895
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;counting.node.consul.		IN	ANY

;; ANSWER SECTION:
counting.node.consul.	0	IN	A	192.168.1.120
counting.node.consul.	0	IN	TXT	"consul-network-segment="

;; Query time: 4 msec
;; SERVER: 192.168.1.151#8600(192.168.1.151) (TCP)
;; WHEN: Thu Jul 27 11:00:58 CEST 2023
;; MSG SIZE  rcvd: 101

Je n’ai pas l’usage de cette fonctionnalité mais cela permet de tester assez facilement qu’une machine est bien présente sur Consul (sans devoir passer par l’UI).


Je tiens également à vous partager cette phrase présente dans la documentation de Consul :

An agent which is already part of a cluster may join an agent in a different cluster, causing the two clusters to be merged into a single cluster.

En français : Si un agent est déjà présent dans un cluster et que nous l’ajoutons à un second cluster, ceux-ci fusionneront pour n’en créer qu’un. (Il est d’ailleurs préférable de n’avoir qu’un cluster Consul par zone géographique, et d’isoler les services si nécéssaires).

Ajouter un service

Voici le tableau représentant les machines de notre cluster :

NomIPDescription
consul-server-01192.168.1.151Machine 01 du cluster
consul-server-02192.168.1.152Machine 02 du cluster
consul-server-03192.168.1.153Machine 03 du cluster
counting192.168.1.120Micro-Service ‘Counting’
dashboard192.168.1.79Micro-Service ‘Dashboard’

Comme premier test, je vais installer un serveur web sur la machine counting et l’ajouter à notre catalogue Consul.

apk add lighttpd
echo "Hello, je suis la machine 'counting'" > /var/www/localhost/htdocs/index.html
service lighttpd start

Le serveur web est fonctionnel sur la machine counting, maintenant je dois informer de l’existence de ce service à notre agent Consul.

Toujours sur la machine counting, je vais créer le fichier /etc/consul.d/lighttpd.hcl.

service {
  name = "lighttpd"
  id = "lighttpd-1"
  port = 80

  check {
    id       = "lighttpd-check"
    http     = "http://localhost:80"
    method   = "GET"
    interval = "1s"
    timeout  = "1s"
  }
}

Avec ce fichier, Consul va apprendre l’existence d’une application sur le port 80 ainsi que d’un moyen de vérifier l’état de santé de ce service.

À retenir :

  • L’id doit être unique (pour identifier l’instance ciblée).
  • Le nom doit être le même si plusieurs machines hébergent le même service.
  • Un healthcheck permet de s’assurer que Consul ne nous redirige pas vers une application morte.
  • Il est possible d’ajouter plusieurs healthchecks.

Information

Ces healthchecks peuvent se présenter sous plusieurs formes : requête HTTP, TCP, UDP ou via un script shell. Je vous laisse avec la documentation officielle qui propose de nombreux exemples : ici

Lighttpd

Je peux maintenant interroger Consul pour obtenir l’IP du micro-service.

  • DNS:
# curl lighttpd.service.consul # en utilisant 192.168.1.151:8600 en serveur DNS
Hello, je suis la machine 'counting'
  • ou via l’API:
  ~ curl -s http://192.168.1.152:8500/v1/catalog/service/lighttpd | jq                                                                                                                                           
[                                                                                                         
  {                                                                                                       
    "ID": "f7fef3ac-ca76-5818-824d-63a08d9d5482",                                                         
    "Node": "counting",                                                                                   
    "Address": "192.168.1.120",                                                                           
    "Datacenter": "coffee",                                                                               
    "TaggedAddresses": {                                                                                  
      "lan": "192.168.1.120",                                                                             
      "lan_ipv4": "192.168.1.120",                                                                        
      "wan": "192.168.1.120",                                                                             
      "wan_ipv4": "192.168.1.120"                                                                         
    },                                                                                                    
    "NodeMeta": {                                                                                         
      "consul-network-segment": ""                                                                        
    },                                                                                                    
    "ServiceKind": "",                                                                                    
    "ServiceID": "lighttpd-1",                                                                            
    "ServiceName": "lighttpd",                                                                            
    "ServiceTags": [],                                                                                    
    "ServiceAddress": "",                                                                                 
    "ServiceWeights": {                                                                                   
      "Passing": 1,                                                                                       
      "Warning": 1                                                                                             
    },                                                                                                            
    "ServiceMeta": {},                                                                                                
    "ServicePort": 80,                                                                                                    
    "ServiceSocketPath": "",                                                                                              
    "ServiceEnableTagOverride": false,                                                                                        
    "ServiceProxy": {                                                                                                         
      "Mode": "",                                                                                                                   
      "MeshGateway": {},                                                                                                            
      "Expose": {}                                                                                                                       
    },                             
    "ServiceConnect": {},           
    "CreateIndex": 1834,                                                
    "ModifyIndex": 1834                                                  
  }                                                                       
]   

Haute disponibilité d’un service

Maintenant, nous allons ajouter une autre machine à notre Consul, celle-ci se nomme dashboard (son nom trouvera son sens plus tard).

Je configure son config.hcl :

datacenter = "coffee"
data_dir = "/opt/consul"
client_addr = "0.0.0.0"
server = false
bind_addr = "0.0.0.0"
advertise_addr = "192.168.1.79" # IP de la machine cliente
retry_join = ["192.168.1.151", "192.168.1.152", "192.168.1.153"]

J’installe lighttpd :

apk add lighttpd
echo "Hello, je suis la machine 'dashboard'" > /var/www/localhost/htdocs/index.html
service lighttpd start

Et j’ajoute le service sur Consul :

service {
  name = "lighttpd"
  id = "lighttpd-2"
  port = 80

  check {
    id       = "lighttpd-check"
    http     = "http://localhost:80"
    method   = "GET"
    interval = "1s"
    timeout  = "1s"
  }
}

Nous avons maintenant deux instances du même service lighttpd sur deux machines différentes :

2 instances

Si j’interroge le service, Consul me redirigera aléatoirement sur l’une des deux machines :

# curl lighttpd.service.consul                                      
Hello, je suis la machine 'dashboard'

En revanche, si j’arrête le serveur web sur la machine dashboard via service lighttpd stop, Consul remarquera une indisponibilité du service grace au healthcheck.

Healtcheck en erreur

Et à la prochaine requête : Consul nous redirigera vers la machine counting contenant le même service.

  • Par DNS :
# curl lighttpd.service.consul
Hello, je suis la machine 'counting'
  • Ou par l’API :
# curl -s http://192.168.1.152:8500/v1/catalog/service/lighttpd | jq -r '.[0].Address'
192.168.1.120

Ce cas pratique nous montre comment héberger deux fois la même application et assurer une redondance.

En résumant :

  • En ajoutant plusieurs applications du même nom : Consul nous redirigera toujours vers une instance fonctionnelle.
  • Les healthchecks déterminent quelles machines peuvent accepter la requête.
  • Il est possible d’obtenir l’IP d’un service ciblé via DNS ou l’API.

Il est également possible de rajouter du “poids” à une instance pour qu’elle soit plus solicitée que les autres (voir ici).

Mise à niveau sans downtime

Jusque là, lorsque nous interrogions Consul : notre seule condition est d’avoir un service en bonne santé.

Mais il est également possible de conditionner notre requête en imposant une application disposant un certain tag.

Pour cela, il est nécéssaire d’utiliser une ‘requête préparée’ (Prepared Query) qui permet d’imposer plusieurs conditions.

Modifions le service lighttpd de la machine dashboard pour y ajouter les tags “prod” et “v2”.

service {
  name = "lighttpd"
  id = "lighttpd-2"
  port = 80
  tags = ["prod", "v2"]
  check {
    id       = "lighttpd-check"
    http     = "http://localhost:80"
    method   = "GET"
    interval = "1s"
    timeout  = "1s"
  }
}

J’en profite aussi pour changer le fichier index.html de cette machine pour mettre le texte : ‘Production (v2)’.

Ajoutons maintenant les tags “prod” et “v1” sur le service lighttpd de la machine counting et changeons le fichier index.html en ‘Production (v1)’.

Les tags d’un service sont visibles depuis l’interface web de Consul :

Tags

Créer une requête préparée

Créons un fichier front-production.json qui définiera le service à contacter ainsi que ses tags.

{
    "Name": "front-production",
    "Service": {
        "Service": "lighttpd",
        "Tags": [
            "v1",
            "prod"
        ]
    }
}

Puis envoyons ce fichier à l’API de Consul pour qu’il le traite :

#curl --request POST --data @front-production.json http://192.168.1.151:8500/v1/query
{"ID":"f2f116c3-e741-e5c9-8f8e-9c3693855826"}

Une réponse indiquant l’ID de la requête nous est retournée.

Remarque

Si vous avez perdu l’ID de la requête, vous pouvez lister les requêtes préparées sur l’endpoint /v1/query. (ex: http://192.168.1.151:8500/v1/query`)

# curl http://192.168.1.151:8500/v1/query | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   457  100   457    0     0   130k      0 --:--:-- --:--:-- --:--:--  148k
[
  {
    "ID": "f2f116c3-e741-e5c9-8f8e-9c3693855826",
    "Name": "front-production",
    "Session": "",
    "Token": "",
    "Template": {
      "Type": "",
      "Regexp": "",
      "RemoveEmptyTags": false
    },
    "Service": {
      "Service": "lighttpd",
      "SamenessGroup": "",
      "Failover": {
        "NearestN": 0,
        "Datacenters": null,
        "Targets": null
      },
      "OnlyPassing": false,
      "IgnoreCheckIDs": null,
      "Near": "",
      "Tags": [
        "prod",
        "v2"
      ],
      "NodeMeta": null,
      "ServiceMeta": null,
      "Connect": false,
      "Peer": ""
    },
    "DNS": {
      "TTL": ""
    },
    "CreateIndex": 3568,
    "ModifyIndex": 3814
  }
]

Nous pouvons maintenant interroger la requête préparée via le DNS de Consul :

nslookup front-production.query.consul
Server:         127.0.0.53
Address:        127.0.0.53#53

Non-authoritative answer:
Name:   front-production.query.consul
Address: 192.168.1.120

Ou l’API :

curl -s http://192.168.1.151:8500/v1/query/f2f116c3-e741-e5c9-8f8e-9c3693855826/execute | jq -r '.Nodes[0].Node.Address'

Une fois la requête préparée, les clients doivent être redirigés vers cette dernière et verront l’application “v1” (présente sur la machine counting).

➜  curl http://front-production.query.consul/
Production (v1)

Mise à jour d’une requête préparée

Maintenant, pour passer de la “v1” à la “v2” de notre application, il suffit de modifier le fichier front-production.json pour changer le tag “v1” en “v2”.

{
    "Name": "front-production",
    "Service": {
        "Service": "lighttpd",
        "Tags": [
            "v2", // <- ICI
            "prod"
        ]
    }
}

Puis d’envoyer le fichier à l’API de Consul en utilisant la méthode PUT et l’ID de la requête préparée : /v1/query/<ID> (sinon une nouvelle requête préparée sera créée).

curl --request PUT --data @front-production.json http://192.168.1.151:8500/v1/query/f2f116c3-e741-e5c9-8f8e-9c3693855826

Essayons maintenant d’interroger la nouvelle version de la requête préparée :

➜  Consul curl -s http://front-production.query.consul/                                                                           
Production (v2)

Nous pouvons alors remarquer que l’application vient d’être mise à jour sans aucun downtime.

Applications en microservices

Dans cette partie, nous allons voir comment Consul peut nous aider à gérer des applications en microservices.

Les microservices sont des applications qui sont découpées en plusieurs services. Chaque service est indépendant et peut être déployé sur une machine différente. (ex: un service pour la base de données, un service pour l’API, un service pour le front-end, etc…)

Les serveurs counting et dashboard vont héberger deux services différents: un service front (dashboard) et un service rest (counting).

Je vais utiliser l’application développée par Hashicorp pour l’exemple : demo-consul-101.

Je dépose les binaires directement sur les serveurs pour simplifier l’exemple, je n’utilise pas de gestionnaire de paquets ou de conteneurs.

Sans Consul

Dans un premier temps, nous allons voir comment fonctionne l’application sans Consul.

Nous démarrons l’application counting sur le port 9003 de la machine counting. Celle-ci est indépendante et ne nécéssite pas un autre service pour fonctionner.

counting:~# PORT=9003 ./counting &

L’application counting est maintenant disponible sur le port 9003 de la machine counting.

micro-service counting


L’application dashboard demande à l’API de counting le nombre de requêtes effectuées sur le service. Il faut donc que l’application dashboard connaisse l’adresse de l’API de counting.

dashboard:~# PORT=9002 COUNTING_SERVICE_URL=http://192.168.1.120:9003 ./dashboard

micro-service dashboard

Cette méthode ne permet pas de gérer la haute disponibilité de l’application counting. Si le service counting tombe en panne, l’application dashboard ne pourra plus fonctionner.

Avec Consul (DNS)

Maintenant, utilisons ce que nous avons vu précédemment pour rendre l’application counting disponible via Consul.

J’ajoute le service counting sur notre catalogue Consul :

# /etc/consul.d/counting.hcl
service {
  name = "counting"
  id = "counting-1"
  port = 9003

  check {
    id       = "counting-check"
    http     = "http://localhost:9003/health"
    method   = "GET"
    interval = "1s"
    timeout  = "1s"
  }
}

Nous pouvons démarrer le service dashboard en utilisant le DNS de Consul pour trouver l’adresse de l’API de counting.

dashboard:~# PORT=9002 COUNTING_SERVICE_URL=http://counting.service.consul:9003 ./dashboard

Ainsi, il est possible de créer un service counting sur plusieurs machines et de les ajouter au catalogue Consul. L’application dashboard pourra alors interroger le service counting via le DNS de Consul et sera redirigée vers un des services counting disponibles.

Consul avec un sidecar (mesh proxy)

Dans cette partie, nous allons voir comment utiliser Consul avec un sidecar, cette méthode permet de rendre une application disponible via un tunnel sécurisé.

Il est possible d’utiliser Envoy ou Consul Connect pour créer un tunnel sécurisé entre les services (voir Consul Connect).

Information

Un sidecar est une application qui est exécutée sur la même machine. Il permet de fournir des fonctionnalités supplémentaires ou de modifier le comportement d’une application.

L’avantage est que l’usage d’un sidecar permet de chiffrer le trafic entre les services. Il est aussi possible de créer des règles pour autoriser ou non les services à communiquer entre eux.

Consul Connect

Par simplicité, nous allons utiliser Consul Connect pour créer un tunnel sécurisé du service dashboard vers le service counting.

Avertissement

À noter qu’il faut que Consul Connect soit activé sur les serveurs Consul avec la configuration suivante :

connect {
  enabled = true
}

Commençons par modifier le fichier /etc/consul.d/counting.hcl de la machine counting :

service {
  name = "counting"
  id = "counting-1"
  port = 9003

  connect {
    sidecar_service {}
  }

  check {
    id       = "counting-check"
    http     = "http://localhost:9003/health"
    method   = "GET"
    interval = "1s"
    timeout  = "1s"
  }
}

Démarrons (si ce n’est pas déjà fait) le service counting :

PORT=9003 ./counting &

Nous démarrons ensuite le sidecar sur la machine counting pour qu’il soit accessible via Consul Connect :

consul connect proxy -sidecar-for counting-1 &

On passe maintenant sur la machine dashboard.

Créons le fichier /etc/consul.d/dashboard.hcl :

service {
  name = "dashboard"
  id = "dashboard-1"
  port = 9002

  check {
    id       = "dashboard-check"
    http     = "http://localhost:9002/health"
    method   = "GET"
    interval = "1s"
    timeout  = "1s"
  }

connect { 
    sidecar_service {
      proxy {
        upstreams = [
          {
            destination_name = "counting"
            local_bind_port  = 5000
          }
        ]
      }
    }
  }
}

Le service dashboard va utiliser le port 5000 pour communiquer avec le service counting via Consul Connect (le port de Counting n’est pas à préciser puisque Consul le connait déjà dans son catalogue).

Celui-ci va directement se connecter au service counting et va créer un tunnel sécurisé entre le service counting et Consul.

Information

À noter que le sidecar n’a pas besoin de connaître quelle machine va communiquer avec lui.

Notre sidecar (de la machine dashboard) va utiliser le port 5000 qui sera redirigé vers le service counting via Consul Connect.

Démarrons le service dashboard et le sidecar :

PORT=9002 COUNTING_SERVICE_URL=http://localhost:5000 ./dashboard &
consul connect proxy -sidecar-for dashboard-1 &

Et voilà, notre application dashboard utilise maintenant Consul Connect pour communiquer avec le service counting.

Avertissement

L’IP utilisée par le sidecar sera 127.0.0.1 par défault. Aucun risque de collision avec les autres services ou d’exposition sur le réseau.


L’usage de Consul Connect permet également de gérer les autorisations entre les services. Il est possible de créer des règles pour autoriser ou non les services à communiquer entre eux.

Par défault : tous les services peuvent communiquer entre eux, mais il est recommandé de créer une règle wildcard pour interdire toutes les communications et de créer les autorisations par la suite. Ces règles se nomment des intentions.

Depuis le service dashboard, on peut voir que counting est défini en tant qu’upstream : Alerte du panel consul

Dans l’onglet Intention de Consul, les règles de communication entre les services sont visibles.

Nous allons créer une première règle pour interdire toutes les communications entre les services :

Deny Intention

Si nous essayons de communiquer avec le service counting depuis le service dashboard, nous obtenons une erreur :

Erreur dans l&rsquo;application Dashboard

Pour autoriser la communication entre nos deux micro-services, il faut créer la règle suivante :

Allow Intention

Information

Je n’autorise QUE dashboard à communiquer avec counting et non l’inverse, counting ne pourra pas contacter dashboard de lui-même.

À partir de ce moment, la communication entre les services est autorisée.

Astuce

Il est aussi possible de créer ces règles via l’API de Consul ou via le CLI.

Création de l’intention wildcard :

# consul intention create -deny "*" "*"
Created: * => * (deny)

Création de l’intention entre les services dashboard et counting :

# consul intention create dashboard counting
Created: dashboard => counting (allow)

Liste des intentions :

# consul intention list
ID                                    Source     Action  Destination  Precedence
1fee94ca-e8f9-b6fb-a348-30415b36363a  dashboard  allow   counting     9
ea8574f3-13cb-6c47-2c63-920caa12364f  *          deny    *            5

Stocker des données (kv store)

Dans cette partie, nous allons voir comment stocker des données dans Consul.

Consul dispose d’un KV store (registre key:value ou clé:valeur en français) qui permet de stocker des données comme des méta-données, des configurations, des informations sur l’environnement, etc.

Je commencerai par un disclaimer : Consul n’est pas fait pour stocker des données sensibles. Ne stockez aucune clé privée, mot de passe, etc. dans Consul !

Pour cela, il existe des solutions comme Vault qui permettent de stocker des données sensibles.

Avertissement

Les objets stockés dans Consul KV sont stockés en clair et peuvent faire une taille maximale de 512Ko.

Pour enregistrer une donnée, il est possible de passer par tous les clients Consul (API, CLI, UI, etc.).

  • Par UI : Première clé

  • En CLI :

consul kv put test/kv "Bonjour"
consul kv put test/kv @fichier.txt # Pour enregistrer le contenu d'un fichier
  • Par l’API :
curl -X PUT "localhost:8500/v1/kv/test/kv" -d "Bonjour ! "
curl -X PUT "localhost:8500/v1/kv/test/kv" -d "@fichier.txt" # Pour enregistrer le contenu d'un fichier

Pour récupérer une donnée, il est possible de passer par l’API, le CLI ou l’UI.

# curl -s http://127.0.0.1:8500/v1/kv/administrator_metadata  | jq -r '.[].Value | @base64d'
{
  "administrators": [
    {
      "name": "Quentin",
      "email": "quentin@une-tasse-de.cafe",
      "username": "thebidouilleur",
      "role": "Administrator"
    },
    {
      "name": "Nougat",
      "email": "nougat@une-tasse-de.cafe",
      "username": "nougat",
      "role": "Cat",
      "birthday": "2016-05-20"
    }
  ]
}
consul kv get test/kv

Astuce

Il est possible de voir toutes les clés stockées dans Consul via l’UI ou via la commande suivante :

consul kv get -recurse

Consul Template

Consul Template est un outil qui permet de générer des fichiers de configuration à partir des données stockées dans Consul KV.

Il prend en input une template et génère un fichier à partir des données récupérées.

Créons un fichier template.ctmpl qui contient la template suivante :

Bonjour {{ key "admin/name" }}

Votre travail est {{ key "admin/job" }}, et votre animal de compagnie est un {{ key "admin/pet_type" }} nommé {{ key "admin/pet" }}

Ce fichier appelle des clés stockées dans notre Consul qui doivent être créées :

consul kv put admin/name "Quentin"
consul kv put admin/pet  "Nougat" # Personne n'a le droit de critiquer le nom de mon chat ;)
consul kv put admin/pet_type "chat"
consul kv put admin/job "System Administrator"

Maintenant, nous allons lancer Consul Template avec la commande suivante :

consul-template -template="template.ctmpl:output.txt"

Celui-ci va générer un fichier output.txt avec le contenu suivant :

# cat output.txt 
Bonjour Quentin

Votre travail est System Administrator, et votre animal de compagnie est un chat nommé Nougat

Le meilleur est que tant que Consul Template est lancé, il va surveiller les données stockées dans Consul et mettre à jour le fichier de configuration si les données changent.

L’idée est alors de générer des fichiers de configuration pour des services comme Nginx, HAProxy, etc. et de les redémarrer automatiquement si ces données sont altérées.

Pour simplifier la configuration de Consul Template, il est possible de créer un fichier de configuration config.hcl pour définir le chemin de la template, le fichier de sortie et même une commande à exécuter après chaque mise à jour du KV Store.

template {
  source = "/opt/consul/templates/template-nginx.ctmpl"
  destination = "/etc/nginx/nginx.conf"
  command = "service nginx restart"
}

Puis on relance Consul Template avec la commande suivante :

consul-template -config=config.hcl

EnvConsul

EnvConsul est un outil qui permet de récupérer des données stockées dans Consul et de les injecter dans l’environnement d’un processus.

Tout comme Consul Template, il est assez simple à utiliser.

Nous créons les même clés que dans la partie précédente :

consul kv put admin/name "Quentin"
consul kv put admin/pet  "Nougat" # Toujours pas le droit de critiquer
consul kv put admin/pet_type "chat"
consul kv put admin/job "System Administrator"

Maintenant, nous lançons la commande envconsul -prefix admin/ [ma commande], celle-ci va s’exécuter avec les variables d’environnement que nous avons précédemment stockées dans Consul.

Par exemple avec la commande env :

#envconsul -upcase -sanitize -prefix admin "env"
USER=root
PAGER=most
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus
PET=Nougat
PET_TYPE=Cat
XDG_SESSION_CLASS=user
TERM=xterm-256color
XDG_SESSION_TYPE=tty
HOME=/root
OLDPWD=/root
SHELL=/bin/bash
LOGNAME=root
XDG_RUNTIME_DIR=/run/user/0
PS1=\[\033[1;31m\]\u\[\033[0;37m\]@\[\033[1;32m\]\h\[\033[00m\]:\[\033[1;34m\]\w\[\033[00m\] (\d - \t)\n\$ 
SSH_TTY=/dev/pts/2
LS_OPTIONS=--color=auto
SHLVL=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=fr_FR.UTF-8
LESSOPEN=| /usr/share/source-highlight/src-hilite-lesspipe.sh %s
_=/usr/bin/envconsul
NAME=Quentin
LESS= -R
XDG_SESSION_ID=178
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=00:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.avif=01;35:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:*~=00;90:*#=00;90:*.bak=00;90:*.old=00;90:*.orig=00;90:*.part=00;90:*.rej=00;90:*.swp=00;90:*.tmp=00;90:*.dpkg-dist=00;90:*.dpkg-old=00;90:*.ucf-dist=00;90:*.ucf-new=00;90:*.ucf-old=00;90:*.rpmnew=00;90:*.rpmorig=00;90:*.rpmsave=00;90:
JOB=System Administrator
PWD=/root/envconsul
MOTD_SHOWN=pam

Les arguments -upcase et -sanitize permettent de mettre en majuscule les clés et de remplacer les caractères invalides par des underscores.

On retrouve bel et bien nos variables d’environnement stockées dans le KV store de Consul.

Pour une application, c’est le même fonctionnement : voici un script en Golang qui va afficher les variables d’environnement (stockées dans consul dans notre cas).

package main

import (
	"fmt"
	"os"
)

func main() {
	nom := os.Getenv("NAME")
	pet := os.Getenv("PET")
	petType := os.Getenv("PET_TYPE")
	job := os.Getenv("JOB")

	if nom == "" || pet == "" || petType == "" || job == "" {
		fmt.Println("Certaines variables d'environnement ne sont pas définies.")
		return
	}

	message := fmt.Sprintf("Bonjour %s\n\nVotre travail est %s, et votre animal de compagnie est un %s nommé %s.", nom, job, petType, pet)
	fmt.Println(message)
  for {
		time.Sleep(3 * time.Minute)
	}
}

Si je lance sans envconsul, j’obtiens une erreur puisque les variables d’environnement ne sont pas définies.

# ./mon-application 
Certaines variables d'environnement ne sont pas définies.

En utilisant envconsul, j’obtiens le résultat attendu.

# envconsul -upcase -sanitize -prefix admin/ ./mon-application 
Bonjour Quentin

Votre travail est System Administrator, et votre animal de compagnie est un chat nommé Nougat.

EnvConsul permet alors de fournir les variables d’environnement à une application en récupérant ces données dans notre Consul.

Le meilleur dans tout ça : envconsul va mettre à jour les variables d’environnement du processus lancé si celles-ci sont modifiées dans le KV store et redémarrer l’application automatiquement.

Chiffrer les échanges

Dans cette partie, nous allons voir comment rajouter une couche de sécurité à notre cluster Consul.

Par défaut, Consul ne chiffre pas les échanges entre les agents Consul (serveurs inclus) ce qui peut poser des problèmes de sécurité. En conditions réelles, il est obligatoire de mettre en place une sécurité contre un MITM.

Créer un certificat pour chaque agent Consul permet d’authentifier ces derniers et de chiffrer les échanges entre eux.

Une méthode possible est de générer une autorité de certification (CA) et de créer des certificats pour chaque agent Consul.

Consul peut faire office de CA nativement sans avoir à installer d’autres outils.

Générer une CA et des certificats pour les serveurs

Sur un des serveurs Consul, nous allons générer une CA et des certificats pour les serveurs Consul.

consul tls ca create

Nous obtenons alors deux fichiers : consul-agent-ca.pem et consul-agent-ca-key.pem à conserver précieusement.

Générons maintenant les certificats pour les autres serveurs Consul (chaque serveur doit avoir son propre certificat).

# consul tls cert create -server -dc coffee
==> WARNING: Server Certificates grants authority to become a
    server and access all state in the cluster including root keys
    and all ACL tokens. Do not distribute them to production hosts
    that are not server nodes. Store them as securely as CA keys.
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
==> Saved coffee-server-consul-0.pem
==> Saved coffee-server-consul-0-key.pem

J’exécute la commande deux fois pour obtenir trois paires de certificats.

total 36
-r--r----- 1 consul   root    227 29 juil. 12:20 coffee-server-consul-0-key.pem
-r--r----- 1 consul   root    977 29 juil. 12:20 coffee-server-consul-0.pem
-r--r----- 1 consul   root    227 29 juil. 12:34 coffee-server-consul-1-key.pem
-r--r----- 1 consul   root    973 29 juil. 12:34 coffee-server-consul-1.pem
-r--r----- 1 consul   root    227 29 juil. 12:34 coffee-server-consul-2-key.pem
-r--r----- 1 consul   root    977 29 juil. 12:34 coffee-server-consul-2.pem
-r--r----- 1 consul   root    227 29 juil. 12:14 consul-agent-ca-key.pem
-r--r----- 1 consul   root   1078 29 juil. 12:14 consul-agent-ca.pem
-r--r----- 1 consul   consul 1018 29 juil. 12:30 consul.hcl

Avertissement

Ces fichiers étant sensibles, je vous invite à restreindre les permissions de ces derniers pour que seul l’utilisateur consul puisse les lire.

Depuis notre poste local, récupérons le fichier consul-agent-ca.pem en vue d’une copie sur les autres serveurs Consul.

cd $(mktemp -d)
scp root@192.168.1.151:/etc/consul.d/consul-agent-ca.pem .
scp consul-agent-ca.pem root@192.168.1.152:/etc/consul.d/
scp consul-agent-ca.pem root@192.168.1.153:/etc/consul.d/

Nous allons maintenant récupérer les fichiers coffee-server-consul-1-key.pem et coffee-server-consul-1.pem et les envoyer sur consul-server-02.

scp root@192.168.1.151:/etc/consul.d/{coffee-server-consul-1-key.pem,coffee-server-consul-1.pem} .
scp {coffee-server-consul-1-key.pem,coffee-server-consul-1.pem} root@192.168.1.152:/etc/consul.d/

Même chose pour le serveur consul-server-03 avec les fichiers coffee-server-consul-2-key.pem et coffee-server-consul-2.pem.

scp root@192.168.1.151:/etc/consul.d/{coffee-server-consul-2-key.pem,coffee-server-consul-2.pem} .
scp {coffee-server-consul-2-key.pem,coffee-server-consul-2.pem} root@192.168.1.153:/etc/consul.d/

Puis, nous allons créer le fichier de configuration tls.hcl sur chaque serveur Consul pour activer le chiffrement TLS.

Voici le fichier sur la machine server-consul-01, pensez à changer le nom des fichiers des certificats en fonction de la machine.

tls {
   defaults {
      ca_file = "/etc/consul.d/consul-agent-ca.pem"
      cert_file = "/etc/consul.d/coffee-server-consul-0.pem"
      key_file = "/etc/consul.d/coffee-server-consul-0-key.pem"

      verify_incoming = true
      verify_outgoing = true
   }
   internal_rpc {
      verify_server_hostname = true
   }
}

auto_encrypt {
  allow_tls = true
}

Avec le fichier de configuration précédent, les échanges entre les serveurs Consul seront chiffrés et un agent avec un certificat non-valide ne pourra pas rejoindre le cluster en tant que serveur.

À noter que ces certificats seront utilisés pour les communications RPC entre les agents Consul ainsi que pour les communications HTTP (UI et API).

Vous pouvez utiliser votre propre CA locale pour générer les certificats. La seule contrainte est que les certificats doivent provenir d’une même CA.

Vault peut également être utilisé pour générer les certificats.

Activer le chiffrement sur les clients

Maintenant que les serveurs communiquent entre eux de manière sécurisée, nous allons activer le chiffrement sur les clients.

Pour rappel, voici les IPs des machines :

NomIPDescription
consul-server-01192.168.1.151Machine 01 du cluster
consul-server-02192.168.1.152Machine 02 du cluster
consul-server-03192.168.1.153Machine 03 du cluster
counting192.168.1.120Micro-Service ‘Counting’
dashboard192.168.1.79Micro-Service ‘Dashboard’

De même que pour les serveurs, nous allons générer un certificat pour chaque client Consul.

consul tls cert create -client -dc coffee
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
==> Saved coffee-client-consul-0.pem
==> Saved coffee-client-consul-0-key.pem

Nous exécutons la commande deux fois pour obtenir deux paires de certificats. Une paire pour chaque machine : counting et dashboard. Une fois les certificats générés, nous les récupèrons sur notre poste local ainsi que le fichier consul-agent-ca.pem (le même que pour les serveurs).

cd $(mktemp -d)
scp root@192.168.1.151:/etc/consul.d/consul-agent-ca.pem .
scp root@192.168.1.151:/etc/consul.d/{coffee-client-consul-0.pem,coffee-client-consul-0-key.pem,coffee-client-consul-1.pem,coffee-client-consul-1-key.pem} .

Nous envoyons la paire de certificats coffee-client-consul-0.pem et coffee-client-consul-0-key.pem sur la machine counting (192.168.1.120) et la paire coffee-client-consul-1.pem et coffee-client-consul-1-key.pem sur la machine dashboard (192.168.1.79). Nous devons également envoyer le fichier consul-agent-ca.pem sur les deux machines.

scp {coffee-client-consul-0.pem,coffee-client-consul-0-key.pem} root@192.168.1.120:/etc/consul.d/ 
scp {coffee-client-consul-1.pem,coffee-client-consul-1-key.pem} root@192.168.1.79:/etc/consul.d/ 
scp consul-agent-ca.pem root@{192.168.1.120,192.168.1.79}:/etc/consul.d/

Nous allons maintenant créer le fichier de configuration tls.hcl sur chaque client Consul pour activer le chiffrement TLS.

tls {
   defaults {
      ca_file = "/etc/consul.d/consul-agent-ca.pem"
      cert_file = "/etc/consul.d/coffee-client-consul-0.pem"
      key_file = "/etc/consul.d/coffee-client-consul-0-key.pem"

      verify_incoming = true
      verify_outgoing = true
   }
   internal_rpc {
      verify_server_hostname = true
   }
}

Avertissement

N’oubliez pas de changer le nom des fichiers en fonction de la machine.

Avec le fichier de configuration précédent, les échanges entre les clients Consul seront chiffrés et un agent avec un certificat non-valide ne pourra pas rejoindre le cluster en tant que client.

Chiffrement du Gossip

Le Gossip (wikipedia) est un protocole de communication utilisé par Consul pour la découverte des agents. Celui-ci va informer les agents Consul de la présence d’un membre dans le cluster ou de sa suppression. Il est indispensable pour le bon fonctionnement de Consul.

Par défaut, le Gossip n’est pas chiffré. Nous allons donc activer le chiffrement de celui-ci.

Il est important de noter que le chiffrement du Gossip est indépendant du chiffrement TLS. Le chiffrement du Gossip n’utilise pas de certificat, c’est un chiffrement symétrique qui sera le même pour tous les agents Consul.

Créons alors notre clé de chiffrement via la CLI Consul.

# consul keygen
45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY=

Nous allons paramétrer le chiffrement du Gossip sur les serveurs et les clients. Pour cela, nous allons créer le fichier gossip.hcl sur chaque machine.

encrypt = "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
encrypt_verify_incoming = true
encrypt_verify_outgoing = true

Une fois le fichier créé, nous allons redémarrer les agents Consul sur chaque machine. Vous n’aurez qu’une petite interruption de service le temps de configurer les clés sur chaque noeud.

Astuce

Il est aussi possible de configurer le chiffrement du Gossip sans interruption !

Pour cela, entrez la clé de chiffrement mais n’activez pas le chiffrement incoming/outgoing:

encrypt = "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
encrypt_verify_incoming = false
encrypt_verify_outgoing = false

Cela permettra aux agents de pouvoir déchiffrer les messages du Gossip mais pas de les chiffrer (les machines peuvent donc continuer à communiquer avec le cluster).

Activons maintenant le chiffrement des messages sortants :

encrypt_verify_outgoing = true

Les agents peuvent envoyer du traffic chiffré, ainsi que recevoir du traffic chiffré et non-chiffré (toujours sans aucune indisponibilité).

Enfin, on peut activer le chiffrement des messages entrants (et refuser les messages non-chiffrés) :

encrypt_verify_incoming = true

Distribuer une nouvelle clé de chiffrement

Admettons maintenant que notre clé est compromise par un noeud infecté. Nous allons donc devoir la changer sur l’ensemble du cluster.

Plutôt que de devoir modifier tous les agents Consul, nous allons simplement distribuer la nouvelle clé de chiffrement sur chaque noeud à partir d’un des serveurs.

Il est possible de voir les clés de chiffrement utilisées par les agents Consul via la commande consul keyring -list.

# cat /etc/consul.d/gossip.hcl 
encrypt = "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
encrypt_verify_incoming = true
encrypt_verify_outgoing = true
# consul keyring -list
==> Gathering installed encryption keys...
WAN:
  45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY= [3/3]

coffee (LAN):
  45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY= [5/5]

Information

La clé de chiffrement n’est pas forcément la même pour le WAN et le LAN. Ici, les clés WAN sont stockées sur les serveurs et les clés LAN sur toutes les machines.

Maintenant, nous allons créer une nouvelle clé de chiffrement et la distribuer sur chaque noeud en utilisant la commande consul keyring.

# consul keygen
DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ=
# consul keyring -install "DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ="
==> Installing new gossip encryption key...
# consul keyring -list
==> Gathering installed encryption keys...

WAN:
  45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY= [3/3]
  DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ= [3/3]

coffee (LAN):
  45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY= [5/5]
  DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ= [5/5]

Puis supprimons l’ancienne clé de chiffrement sur chaque noeud.

# consul keyring -remove "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
==> Removing gossip encryption key...
error: Unexpected response code: 500 (10 errors occurred:
	* WAN error: 3/3 nodes reported failure
	* consul-server-01.coffee: Removing the primary key is not allowed
	* consul-server-02.coffee: Removing the primary key is not allowed
	* consul-server-03.coffee: Removing the primary key is not allowed
	* coffee (LAN) error: 5/5 nodes reported failure
	* consul-server-01: Removing the primary key is not allowed
	* counting: Removing the primary key is not allowed
	* consul-server-03: Removing the primary key is not allowed
	* consul-server-02: Removing the primary key is not allowed
	* dashboard: Removing the primary key is not allowed)

Une erreur est apparue car Consul ne vous permettra pas de supprimer une clé de chiffrement si elle est utilisée. Nous devons d’abord indiquer de quelle clé de chiffrement nous souhaitons faire usage.

# consul keyring -use "DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ="
==> Changing primary gossip encryption key...
# consul keyring -remove "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
==> Removing gossip encryption key...

Nous avons désormais une nouvelle clé de chiffrement sur l’ensemble du cluster.

# consul keyring -list
==> Gathering installed encryption keys...

WAN:
  DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ= [3/3]

coffee (LAN):
  DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ= [5/5]

Grâce à cette méthode, nous pouvons changer la clé de chiffrement sur l’ensemble du cluster sans avoir à modifier les fichiers de configuration sur chaque noeud. Hashicorp propose même un exemple de script Bash que vous pouvez exécuter régulièrement pour changer la clé de chiffrement.

#!/usr/bin/env bash

# Setup Consul address info
export CONSUL_HTTP_ADDR="http://localhost:8500"

# Generate the new key
NEW_KEY=`consul keygen`

# Install the key
consul keyring -install ${NEW_KEY}

# Set as primary
consul keyring -use ${NEW_KEY}

# Retrieve all keys used by Consul
KEYS=`curl -s ${CONSUL_HTTP_ADDR}/v1/operator/keyring`

ALL_KEYS=`echo ${KEYS} | jq -r '.[].Keys| to_entries[].key' | sort | uniq`

for i in `echo ${ALL_KEYS}`; do
  if [ $i != ${NEW_KEY} ] ; then
    consul keyring -remove $i
  fi
done

Créer des ACLs

Depuis le début de cet article, nous utilisons la CLI Consul, ainsi que l’interface web comme bon nous semble. Cependant, dans un environnement de production, il est important de limiter l’accès à Consul et de définir des permissions pour les utilisateurs.

Sur Consul, nous avons les ACLs (à ne pas confondre avec les Intentions pour les services). Les ACLs permettent de définir des permissions pour les utilisateurs et les applications.

Activer les ACLs

Lors de la première activation des ACLs, nous allons paramétrer une politique de base par défaut. Il est obligatoire (pour le moment) de définir une politique par défaut qui autorise les échanges, sous peine de ne plus pouvoir accéder à Consul.

Nous allons alors nous connecter sur chaque serveur consul et créer le fichier acl.hcl avec le contenu suivant :

# /etc/consul.d/acl.hcl
acl {
  enabled = true
  default_policy = "allow"
  down_policy = "extend-cache"
}

On répète l’opération sur chaque serveur Consul et on redémarre le service Consul.

Une fois les ACLs activées, nous allons créer le token de bootstrap. Ce token dispose des droits absolus sur Consul. Il est donc important de le conserver précieusement.

# consul acl bootstrap
AccessorID:       4e5ad15a-84f4-b7aa-c5d4-66561f931860
SecretID:         26ccba2f-b97f-31ba-6447-b5f3e2cf7858
Description:      Bootstrap Token (Global Management)
Local:            false
Create Time:      2023-07-29 16:10:56.933406514 +0200 CEST
Policies:
   00000000-0000-0000-0000-000000000001 - global-management

Par défaut, lorsque nous ne sommes pas authentifiés, nous avons accès à toutes les fonctionnalités de Consul qui ne concernent pas les ACLs (grâce à la default_policy que nous avons défini plus haut).

Si nous essayons d’accéder à une fonctionnalité qui concerne les ACLs, nous obtenons une erreur :

# consul acl token list
Failed to retrieve the token list: Unexpected response code: 403 (Permission denied: anonymous token lacks permission 'acl:read'. The anonymous token is used implicitly when a request does not specify a token.)

Pour palier à ce problème, nous pouvons utiliser le token de bootstrap. Cependant, ce token est très puissant et nous ne devons pas l’utiliser pour les opérations courantes (nous allons régler ce problème par la suite).

Définissons une variable d’environnement CONSUL_HTTP_TOKEN dont la valeur est le SecretID du token de bootstrap.

#export CONSUL_HTTP_TOKEN=26ccba2f-b97f-31ba-6447-b5f3e2cf7858
#consul acl token list
AccessorID:       4e5ad15a-84f4-b7aa-c5d4-66561f931860
SecretID:         26ccba2f-b97f-31ba-6447-b5f3e2cf7858
Description:      Bootstrap Token (Global Management)
Local:            false
Create Time:      2023-07-29 16:10:56.933406514 +0200 CEST
Policies:
   00000000-0000-0000-0000-000000000001 - global-management

AccessorID:       00000000-0000-0000-0000-000000000002
SecretID:         anonymous
Description:      Anonymous Token
Local:            false
Create Time:      2023-07-29 16:07:21.146019865 +0200 CEST

La policy global-management (00000000-0000-0000-0000-000000000001) permet d’accéder à toutes les fonctionnalités de Consul, il n’est pas possible de l’éditer ou de la supprimer (en revanche, il est possible de la renommer).

Une fois notre token de bootstrap créé, nous allons modifier le fichier de configuration acl.hcl sur chaque serveur Consul pour que la default_policy soit deny. Ainsi, si nous ne sommes pas authentifié, nous n’avons aucun droit et aucun accès.

acl {
  enabled = true
  default_policy = "deny"
  down_policy = "extend-cache"
}

Après avoir redémarré les services Consul, la commande consul members ne devrait rien nous renvoyer si nous ne définissons pas la variable d’environnement CONSUL_HTTP_TOKEN avec la valeur du SecretID du token de bootstrap.

root@consul-server:~ (dim. juil. 30 - 15:40:06)
# unset CONSUL_HTTP_TOKEN
root@consul-server:~ (dim. juil. 30 - 15:40:11)
# consul members
root@consul-server:~ (dim. juil. 30 - 15:40:12)
# export CONSUL_HTTP_TOKEN=26ccba2f-b97f-31ba-6447-b5f3e2cf7858
root@consul-server:~ (dim. juil. 30 - 15:40:15)
# consul members
Node              Address             Status  Type    Build   Protocol  DC      Partition  Segment
consul-server-01  192.168.1.151:8301  alive   server  1.16.0  2         coffee  default    <all>
consul-server-02  192.168.1.152:8301  alive   server  1.15.4  2         coffee  default    <all>
consul-server-03  192.168.1.153:8301  alive   server  1.15.4  2         coffee  default    <all>
counting          192.168.1.120:8301  alive   client  1.15.4  2         coffee  default    <default>
dashboard         192.168.1.79:8301   alive   client  1.15.4  2         coffee  default    <default>

Astuce

Si vous perdez le token de bootstrap, il est possible de réinitialiser les ACLs en ligne de commande. Vous pouvez consulter la documentation officielle pour plus d’informations : access-control-troubleshoot

Créer des policies

Une policy est un ensemble de règles qui définissent les permissions d’un utilisateur ou d’une application. Il sera possible de lier un token à une (ou plusieurs) policy.

Les différentes permissions sont les suivantes :

  • read (autoriser à lire les données, mais pas à les modifier)
  • write (autoriser à lire et modifier les données)
  • deny (interdire l’accès à une ressource)
  • list (autoriser à lister les ressources KV)

Il est possible d’appliquer ces permissions sur les ressources suivantes :

  • agent (pour les opérations sur l’agent)
  • node (pour les opérations sur les noeuds)
  • service (pour les opérations sur le catalogue de services)
  • key (pour les opérations sur le KV store)
  • session (pour les opérations sur les sessions)
  • operator (pour les opérations sur le cluster)
  • acl (pour les opérations sur les policies et tokens)

Pour créer une policy, nous allons utiliser la commande consul acl policy create :

Mais avant ça, nous allons pouvoir décrire notre policy dans un fichier .hcl et l’appliquer avec la commande consul acl policy create -name <nom de la policy> -rules <fichier .hcl>.

Astuce

Si la permission par défaut est deny, il vous faudra une policy pour rejoindre le cluster ou pour créer un service.

Par exemple :

node "counting" {
  policy = "write"
}
service "counting" {
  policy = "write"
}

Mise en place d’ACL pour les applications

Après l’activation des ACLs et de la policy par défaut deny, les applications counting et dashboard ne sont plus présentes dans le catalogue de services et ne peuvent plus communiquer avec Consul Connect.

Application indisponible

Nous allons commencer par créer une policy pour l’application counting, qui aura les permissions pour rejoindre le cluster en tant que counting et pour créer un service counting.

# counting.hcl
node "counting" {
  policy = "write"
}

service_prefix "counting" {
  policy = "write"
  intentions = "read"
}

Quant à la la policy pour l’application dashboard dashboard.hcl, qui aura les permissions pour rejoindre le cluster en tant que dashboard, pour créer un service dashboard, pour lire les services commençant par counting et enfin pour lire les informations des noeuds commençant par counting.

# dashboard.hcl
node "dashboard" {
  policy = "write"
}

service_prefix "dashboard" {
  policy = "write"
}

service_prefix "counting" {
  policy = "read"
}

node_prefix "counting" {
  policy = "read"
}

Nous appliquons maintenant les deux policies avec la commande consul acl policy create -name <nom de la policy> -rules <fichier .hcl>.

#consul acl policy create -name counting-policy -rules @counting.hcl
#consul acl policy create -name dashboard-policy -rules @dashboard.hcl
ID:           fb76be74-aaa4-8388-3ce0-c143ee21ce2e
Name:         counting-policy
Description:  
Datacenters:  
Rules:
# counting.hcl
node "counting" {
  policy = "write"
}

service_prefix "counting" {
  policy = "write"
  intentions = "read"
}
---
ID:           0fbb5905-2203-6462-56f6-e65eb2a0243b
Name:         dashboard-policy
Description:  
Datacenters:  
Rules:
# dashboard.hcl
node "dashboard" {
  policy = "write"
}

service_prefix "dashboard" {
  policy = "write"
}

service_prefix "counting" {
  policy = "read"
}

node_prefix "counting" {
  policy = "read"
}

Nous allons maintenant créer un token correspondant à chaque application, en lui associant la policy correspondante.

consul acl token create -description "Token pour machine 'counting'" -policy-id fb76be74-aaa4-8388-3ce0-c143ee21ce2e
consul acl token create -description "Token pour machine 'dashboard'" -policy-id 0fbb5905-2203-6462-56f6-e65eb2a0243b

Nous obtenons les tokens suivants :

  • df228746-fb24-837d-cbc3-c4ad80aff27a pour l’application counting
  • 2748f8e2-f260-76a1-325f-81363490c757 pour l’application dashboard

Nous créons ensuite un fichier de configuration sur les deux machines avec le token correspondant.

acl = {
  enabled = true
  tokens = {
    default = "2748f8e2-f260-76a1-325f-81363490c757"
  }
}

Après avoir redémarré les deux applications, nous pouvons maintenant accéder à l’application dashboard et counting.

Application disponible

Grâce à ces ACLs, chaque machine dispose du minimum de permissions pour fonctionner.

Différente manière d’utiliser un token

En UI, il vous faudra vous connecter en donnant un token valide. Vous pouvez utiliser le token de bootstrap ou un token que vous avez créé.

En ligne de commande, il existe plusieurs manières de spécifier un token :

  • en utilisant la variable d’environnement CONSUL_HTTP_TOKEN
  • en utilisant l’option -token et en spécifiant le token
  • en utilisant l’option -token-file et en spécifiant le chemin vers le fichier contenant le token

Et par l’API REST, il vous faudra spécifier le token dans le header X-Consul-Token.

curl --silent --header "X-Consul-Token: d74b1733-b368-51b0-85d7-f5e06cea804d" http://localhost:8500/v1/kv/apps/eCommerce/version

Modifier les permissions par défaut

Il se peut que vous souhaitiez modifier les permissions par défaut pour permettre des actions sans devoir s’authentifier.

Par exemple, nous souhaitons permettre à toutes les machines de lister les services et les noeuds. Par défaut, voici les réponses obtenues :

# consul members
# consul catalog services
No services match the given query - try expanding your search.

Pour donner cette autorisation, nous pouvons créer une policy qui aura les permissions read sur les services et les noeuds.

Policy par défault

Pour l’appliquer à toutes les requêtes non-authentifiées, nous devons lier cette policy au token Anonymous.

Celui-ci est créé par défaut, et possède l’ID 00000000-0000-0000-0000-000000000002.

Depuis l’UI, nous devons alors éditer le token Anonymous et lui associer la policy default-permissions.

Token Anonymous

# consul members
Node              Address             Status  Type    Build   Protocol  DC      Partition  Segment
consul-server-01  192.168.1.151:8301  alive   server  1.16.0  2         coffee  default    <all>
consul-server-02  192.168.1.152:8301  alive   server  1.15.4  2         coffee  default    <all>
consul-server-03  192.168.1.153:8301  alive   server  1.15.4  2         coffee  default    <all>
counting          192.168.1.120:8301  alive   client  1.15.4  2         coffee  default    <default>
dashboard         192.168.1.79:8301   alive   client  1.15.4  2         coffee  default    <default>
# consul catalog services
consul
counting
counting-sidecar-proxy
dashboard
dashboard-sidecar-proxy

(Bonus) Créer un token pour créer des snapshots

Pour créer un token qui aura les permissions pour générer des snapshots, il faut ajouter une policy qui aura les permissions write sur les ACLs.

consul acl policy create -name "snapshot-policy" -rules 'acl = "read"'
consul acl token create -description "Snapshot Token" -policy-name "snapshot-policy"

J’aurais bien aimé pouvoir faire mes sauvegardes avec une policy qui aurait uniquement la permission read sur les snapshots, mais cela semble impossible.

# consul snapshot save toto
Error saving snapshot: Unexpected response code: 403 (Permission denied: token with AccessorID '3bfc0b27-8a11-c791-3cab-0b3359b43e93' lacks permission 'acl:write')

Conclusion

J’avais prévu de faire un chapitre sur le fait d’interconnecter plusieurs clusters Consul et présenter Consul dans un cluster Kubernetes, mais après ces > 1700 lignes de markdown et le temps que j’ai passé à reproduire les différents cas, je pense que je vais m’arrêter là.

Surtout pour un article qui n’intéressera qu’un publique très restreint.

Il reste néanmoins plus qu’assez de matière pour faire d’autres articles (Consul dans Kubernetes, Extraction de métriques dans Consul Connect, Datacenter Federation, etc.), mais je pense que je vais faire une pause sur Consul pour le moment et me concentrer sur une potentielle certification HashiCorp (si j’en ai le temps et la foi).

J’espère que cet article vous aura plu et qu’il vous aura permis de mieux comprendre Consul et ses fonctionnalités !


Je ne le partage pas beaucoup, mais je possède une page Ko-Fi vous permettant de me faire un don (sans créer de compte) si vous souhaitez (et pouvez vous le permettre) soutenir financièrement mes projets.

ko-fi

Liens utiles