Website Logo. Upload to /source/logo.png ; disable in /source/_includes/logo.html

Zuzur’s wobleg

technology, Internet, and a bit of this and that (with some dyslexia inside)

Rancher: Sécurisation Du Serveur

| Comments

2ème épisode des billets sur rancher (le premier billet est )

J’ai discuté auparavant de l’installation d’un serveur rancher et d’un agent sur une seule machine, pour faire des tests ou héberger sa propre infrastructure docker sur une petite machine.

Dans ce billet, je vais discuter de la sécurisation du serveur rancher

Après l’installation, le conteneur du serveur rancher écoute sur le port 8080, sans SSL ni aucune protection pour le mot de passe d’administration (que je vous conseille assez fort - généralement, pour ce genre de trucs, j’utilise openssl rand -hex 32 et je stocke le résultat dans mon gestionnaire de mots de passe).

Puisqu’on parle d’une machine hostée - pour ma part, il s’agit d’une Dedibox, il est important de la sécuriser un minimum - ça serait dommage que votre machine devienne membre d’un spambot juste parce que vous n’avez pas pris ces précautions…

On pourrait ensuite très facilement déployer un conteneur haproxy dans rancher qui expose notre serveur d’admin rancher - mais évidemment, on a un problème de poule et d’oeuf… si votre rancher est en panne, et que le conteneur haproxy n’est pas correctement déployé… vous ne pouvez plus gérer votre cluster. C’est dommage…

On va donc déployer un conteneur nginx - pas géré par rancher, mais fonctionnant dans le serveur docker où fonctionne notre rancher - et qui écoute sur un port non standard (j’ai choisi le port 4430).

le problème des certificats

si on veut protéger les communications avec l’extérieur, il nous faut un certificat valable pour notre serveur. J’utilise letsencrypt (package letsencrypt) pour ça, c’est plutôt facile à mettre en place.

Attention cependant, pour ma part, j’utilise souvent l’option standalone du client letsencrypt, et les méthodes de validation http-01, tls-sni-01, utilisées par défaut, ont besoin d’un serveur HTTP qui écoute sur les ports 80 et 443 (pour valider que le serveur pour lequel vous cherchez à émettre un certificat est bien le vôtre). Avec ces deux méthodes de validation, avant d’émettre des certificats, le serveur letsencrypt doit se connecter au serveur pour récupérer un secret partagé, la stack que vous allez déployer exposera probablement ces ports standards, et il faudra donc arrêter toute la stack pour obtenir et/ou renouveler le certificat de votre serveur rancher.

Il faut donc mettre en place et tester une autre méthode de validation - dns-01 (le challenge est inséré dans un record TXT de la zone) pour letsencrypt si on ne veut pas avoir à arrêter tous les services pour renouveler le certificat du serveur rancher.

on pourrait parfaitement imaginer que la stack contienne un service pour gérer les certificats avec letsencrypt - ça se fait très facilement, j’en parlerai dans un prochain billet, mais pour ce certificat en particulier, faire ça reviendrait à créer un nouveau problème de poule et d’oeuf. Si le certificat de votre serveur expire, la stack entière risque de ne plus fonctionner, et avec elle le système de renouvellement automatique…

certificats auto-signés ?

D’expérience, il est difficile (voire impossible) de mettre en place un certificat auto-signé avec rancher - beaucoup de composant dans rancher (serveur, agent, serveur de meta-données, gestionnaire de réseau, healthcheck, les outils en ligne de commande, etc…) communiquent avec le serveur et chacun a besoin d’un moyen de valider le certificat (il faudrait donc le passer à tous ces composants, et il n’y a pas - à ma connaissance - d’option dans tous ces composants pour arrêter la validation des certificats).

L’effort nécessaire pour le faire est selon moi bien plus important qu’avec un certificat valide obtenu avec letsencrypt. Et comme tout bon admnistrateur système, je suis feignant à l’extrême, alors… ;-)

terminaison SSL avec nginx

Pour faire la terminaison SSL, on va lancer un conteneur nginx qui va s’occuper de forwarder tout au serveur rancher. Un truc auquel il faut faire attention, c’est que l’interface utilisateur de rancher utilise les web-sockets, donc votre nginx doit supporter les entêtes Upgrade: ... et Connection: Upgrade.

J’ai au préalable récupéré un certificat valable et sa clé que le client letsencrypt a placé dans /etc/letsencrypt/live/rancher.arzur.net/.

La configuration suivante fonctionne bien - il y a bien sûr plein de variations possibles - j’ai simplement copié et adapté celle qui se trouve dans la documentation de rancher :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# mkdir -p /opt/rancher/nginx/conf.d
# cat <<EOF > /opt/rancher/nginx/conf.d/rancher.conf
upstream rancher {
    server rancher:8080;
}

server {
    listen 4430 ssl http2;
    server_name rancher.arzur.net;
    ssl_certificate /etc/letsencrypt/live/rancher.arzur.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/rancher.arzur.net/privkey.pem;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://rancher;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        # This allows the ability for the execute shell window to remain open for up to 15 minutes. Without this parameter, the default is 1 minute and will automatically close.
        proxy_read_timeout 900s;
    }
}

server {
    listen 8080;
    server_name rancher.arzur.net;
    return 301 https://$server_name:4430$request_uri;
}
EOF
#

On note:

  • 1 upstream rancher qui se connecte à un serveur nommé rancher sur le port 8080. On va donc devoir faire en sorte que docker lie le conteneur du serveur rancher avec notre conteneur nginx en le nommant ‘rancher’
  • le serveur écoute sur le port 8080 (pour faire la redirection HTTP -> HTTPS) et 4430. Dans le billet précédent, on avait exposé le port 8080 du serveur rancher. Si on démarre le conteneur nginx en même temps, il va y avoir un conflit, il faut donc redémarrer le conteneur rancher-server en n’exposant aucun port. La communication directe avec celui-ci ne sera alors possible que par l’intermédiaire du démon docker…
  • nginx va aller chercher les certificats dans /etc/letsencrypt/live/..., on va donc devoir monter un volume docker pour que nginx puisse lire les données depuis le répertoire où se trouvent le certificat et la clé privée. On ne peut pas utiliser de liens symboliques !!! il faut copier physiquement les fichiers ou monter le chemin dans le conteneur car celui-ci n’a pas accès au reste du système de fichiers et ne pourra donc pas lire la cible des liens éventuels…

On peut maintenant tirer l’image nginx depuis le docker hub. J’utilise généralement les conteneur basés sur alpine car ils sont généralement tout petits (autour de 15MB) et ne contiennent que ce dont on a besoin pour faire fonctionner le service.

1
2
3
4
5
6
7
8
root@db3:/opt/rancher/nginx/conf.d# docker pull nginx:alpine
alpine: Pulling from library/nginx
6f821164d5b7: Already exists
43dfefee8e05: Already exists
a93f78d7923c: Already exists
a9ea3624aa7f: Already exists
Digest: sha256:f36b1e2a2b62a05b25049b2ec09df7e3181e3e24c5758350578bfe1ee594176f
Status: Downloaded newer image for nginx:alpine

On arrête le serveur, on le redémarre après avoir fait un backup de l’ancien conteneur (toujours garder le truc qui fonctionne quand on fait des expériences comme ça…)

1
2
3
4
# docker stop rancher-server
# docker rename rancher-server rancher-server-20170614 # on garde  l'ancien conteneur au cas où ça merde !
# docker run -d -v /opt/rancher/mysql:/var/lib/mysql --restart=unless-stopped --name rancher-server rancher/server:stable # (cette fois le port 8080 n'est pas exposé)
# docker run -d --name rancher-nginx -p 8080:8080 -p 4430:4430 --link=rancher-server:rancher -v /opt/rancher/nginx/conf.d:/etc/nginx/conf.d -v /etc/letsencrypt/live:/etc/letsencrypt/live:ro nginx:alpine

On peut vérifier qu’on peut se connecter sur le port 4430 du serveur et qu’on y a accès. C’est magique ! :-D On note l’option --link rancher-server:rancher qui établit un lien entre rancher-nginx et rancher-server. Cela met en place ce qu’il faut pour que toute connexion dand le conteneur nginx a un serveur nommé rancher sera envoyée au conteneur rancher-server. On aussi qu’on peut utiliser l’option ro (read-only) quand on monte des volumes, ainsi le conteneur ne pourra pas modifier la base des certificats gérés par letsencrypt.

Si vous avez suivi l’article précédent, comme on a mis en place une redirection du port 8080 en HTTP au port 4430 en HTTPS, tout devrait fonctionner normalement. On peut tout à fait démarrer un nouvel agent avec la nouvelle adresse (https://rancher.arzur.net:4430/…) en n’oubliant pas le token de validation que l’interface a fourni quand on l’a démarré, après avoir arrêté et supprimé l’ancien (docker stop rancher-agent)1.

Une fois tout cela mis en place, on a un serveur rancher qui me semble suffisamment sécurisé. Avec le firewall, je restreint en plus l’accès aux quelques adresses IP d’où je pourrais me connecter…

On va pouvoir (enfin !) passer au déploiement de notre première stack de service… en commençant par mettre en place les outils en ligne de commande pour gérer le cluster…


  1. quand on redémarre l’agent ou le serveur, les conteneurs déjà déployés par rancher ne sont pas perturbés. Ils continuent à fonctionner normalement - seuls les conteneurs qui ont besoin du service de méta-données de rancher seront perturbés - mais j’ai testé à de nombreuses reprises que ça ne perturbe pas le fonctionnement d’un jeu de services déjà déployé.

Comments