Introduction
Dans notre premier article, nous avons parlé de la mise en place de webhooks github pour déployer automatiquement le blog sur notre serveur de production. Pour cela, nous utilisions une librairie écrite en Go : Githook.
Néanmoins, à l’époque, nous n’utilisions pas cette fonctionnalité de manière sécurisée. N’importe quel attaquant pouvait, sous réserve de trouver la bonne route et le bon port, requêter en permanence notre serveur de webhook déclenchant le script de mise en production. En effet, Githook ne permettait pas d’utiliser cette fonctionnalité-là.
J’ai donc décidé d’implémenter rapidement un serveur léger pouvant remplir le même rôle que Githook, mais de manière sécurisée, en ne laissant pas n’importe quel appelant pouvoir déclencher le script de migration.
Développement
Principe des webhooks sécurisés
Le principe est simple : il s’agit tout d’abord de choisir un secret et de le rendre disponible chez Github (dans la zone de configuration webhooks) ainsi que sur notre serveur de production. Les deux parties qui vont discuter ensemble possèderont ce secret, mais aucune autre ne l’aura. Ce qui rendra impossible toute requête externe.
Github va utiliser le secret pour “Sha1-hasher” de son côté le contenu du payload qu’il va renvoyer dans sa requête à notre serveur. Il renseignera ce hash dans l’attribut X-Hub-Signature
. Il s’agit en fait de signer la requête pour que notre serveur sache que celle-ci est légitime et provient d’une entité connue (Github).
Du côté de notre serveur, nous recevrons la requête émise par Github, et nous calculeront nous aussi le Sha1 du payload, avec notre secret disponible côté serveur. Si le Hash fournit par l’appelant est le même que celui calculé côté serveur, alors, nous pourrons déclencher le script de déploiement. Si les deux sont différents, nous renvoyons une erreur et le script ne sera pas exécuté.
Développement
3 étapes principales :
- Fournir la configuration du serveur :
- Port d’écoute
- Route
- Secret
- Script à exécuter
- Calculer le hash et le comparer à celui fourni par Github
- Exécuter le script sous certaines conditions
- Fournir un reporting précis et détaillé
Le secret devant être disponible côté serveur de manière persistante, deux possibilités peuvent être envisagées :
- Variable d’environnement
- Fichier Json
Pour sa praticité, j’ai choisi d’implémenter une solution à base de fichier json pour le stockage du secret.
Quelques remarques techniques
- Utilisation de la librairie
commander
pour la mise en place des paramètres invoqués en ligne de commande. - Utilisation de la librairie
crypto
pour les calculs cryptographiques : hash et comparaison en temps constant (pour éviter les attaques temporelles). - Utilisation de
shelljs
pour déclencher l’exécution du script. - Notre serveur renvoie des réponses précises en cas d’erreurs spécifiques :
500
si le script déclenche une erreur, avec les messages d’exécutions et d’erreur du shell,403
si la requête n’est pas légitime (les signatures ne correspondent pas) ou bien200
si tout va bien. Ces réponses sont consultables depuis l’interface Github et fournissent des informations intéressantes sur le bon fonctionnement du script.
La librairie developpée pour cet article est disponible sur ce repository git !
Mise en place du serveur qui traitera le webhook sous debian
Pour mettre en place notre serveur fraîchement développé, nous allons configurer un service systemd pour le faire tourner en tâche de fond. Je ne suis pas vraiment familier avec ce genre de choses, mais voici une brève introduction à systemd
et à ses commandes de base
Les commandes systemd
systemctl start $_{SERVICE_NAME} : Démarrer un servicesystemctl stop $_{SERVICE_NAME} : Arrêter un servicesystemctl status $_{SERVICE_NAME} : Obtenir le status d'un servicesystemctl restart $_{SERVICE_NAME} : Redémarrer un servicesystemctl daemon-reload : Recharger les services après une modification de confjournalctl -u $_{SERVICE_NAME} : Afficher les logs d'un service
Le fichier de configuration d’un service
Pour configurer un nouveau service systemd :
- Créer un fichier pour notre nouveau service
touch /etc/systemd/system/$_{SERVICE_NAME}.service
- Il faut remplir ce fichier avec le template suivant :
[Unit]Description=Webhook server[Service]ExecStart=/path/to/node/webhooks/index.js --port $_{PORT} --route $_{ROUTE} --conf $_{PATH_TO_CONF_FILE} --script $_{PATH_TO_SHELL_SCRIPT}Restart=alwaysUser=$_{USER}Group=$_{GROUP_OF_USER}Environment=PATH=/usr/bin:/usr/local/binEnvironment=NODE_ENV=productionWorkingDirectory=/path/to/node/webhooks[Install]WantedBy=multi-user.target
Où :
/path/to/node/webhooks/
est le chemin vers la librarie développée pour cet article.$_{USER}
et$_{GROUP_OF_USER}
désigne l’utilisateur principal ainsi que son groupe, depuis lesquels le service sera lancé.
Après avoir créé ce fichier, il suffit de :
- Recharger les fichiers de conf avec
systemctl daemon-reload
- Lancer notre nouveau service
systemctl start $_{SERVICE_NAME}
Normalement, si tout va bien, notre service est lancé et nous pouvons voir les logs s’afficher avec journalctl -u $_{SERVICE_NAME}
Conclusion
Grâce à la simplicité d’utilisation de Node.js et de son écosystème, nous avons pû facilement développer, tester et mettre en production un serveur de réponse aux webhook Github. Ce serveur, et c’est la raison même de son existence, propose un système de vérification de signatures permettant de rejeter toute requête illégitime, faite par une partie ne connaissant pas le secret propre à nos deux parties : Github et notre serveur privé.