2 points par GN⁺ 2024-09-26 | 1 commentaires | Partager sur WhatsApp
  • BlogTech est une boîte à outils en C pour gérer des sites web de petite à moyenne taille, avec HTTPS, hôtes virtuels, gestion automatique des certificats via ACME et client d’administration de serveur à distance
  • Le projet est actuellement en phase de test ; il est en version 0.4.x et a été utilisé pour exploiter https://coz.is/ pendant quelques semaines, mais il n’est pas présenté comme une version stable
  • Le serveur fonctionne sous Linux et Windows, mais HTTPS n’est pris en charge que sous Linux ; sous Windows, le client comme le serveur ne peuvent utiliser que HTTP
  • La gestion de fichiers à distance se fait avec --put, --get et --delete, et les requêtes qui modifient des ressources sont authentifiées par une signature HMAC/SHA256 fondée sur un secret partagé
  • En production, il faut un serveur HTTP sur le port 80 et des enregistrements DNS de domaine ; si ACME est activé, les fichiers de certificat et de clé privée sont générés automatiquement, et les erreurs sont consignées dans acme.log

Objectif et état actuel de BlogTech

  • BlogTech est une boîte à outils pour gérer des sites web de petite à moyenne taille
  • Les fonctionnalités prises en charge sont les suivantes
    • HTTPS

    • Hôtes virtuels

      • Gestion automatique des certificats via ACME
      • Client pour l’administration distante du serveur
      • Encore en phase de test
      • Un exemple d’utilisation réussie a servi https://coz.is/ pendant quelques semaines avec la version 0.4.x
      • L’ancienne version monofichier se trouve dans la branche single_file

Compilation et exécution de base

  • BlogTech fonctionne sous Linux et Windows
    • La compilation Linux nécessite les bibliothèques de développement OpenSSL et gcc
    • La compilation Windows nécessite clang
  • La compilation s’effectue avec des scripts propres à chaque plateforme
    • Linux : ./build.sh
    • Windows : .\build.bat
  • Les artefacts de compilation sont les suivants
    • Linux : blogtech
    • Windows : blogtech.exe
  • Sous Linux, l’installation est possible avec ./install.sh
  • Un exemple d’exécution de serveur de base consiste à créer un répertoire docroot et à spécifier --serve, --document-root=docroot et --skip-auth-check
  • Le serveur HTTP de base écoute sur 127.0.0.1:8080 et sert le contenu de docroot
  • Si docroot est vide, l’accès à http://127.0.0.1:8080/ renvoie une 404

Gestion de fichiers à distance et authentification

  • BlogTech peut téléverser des fichiers vers le serveur en mode client
    • téléversement de fichier avec --put
    • téléchargement d’un fichier distant avec --get
    • suppression d’un fichier distant avec --delete
  • Le fonctionnement est identique lorsque le client et le serveur tournent sur la même machine ou vis-à-vis d’un serveur sur une machine distante
  • Les requêtes qui modifient des ressources sont signées numériquement avec une signature HMAC/SHA256
  • Le client et le serveur doivent partager le même secret, stocké par convention dans un fichier admin.pwd
  • Le fichier de mot de passe est indiqué avec l’option --auth-password-file
  • En l’absence de fichier de mot de passe, toutes les requêtes nécessitant une authentification sont rejetées
  • Les mots de passe vides sont également rejetés
  • Pendant le développement, --skip-auth-check permet de traiter toutes les requêtes comme authentifiées
    • Avec cette option, --auth-password-file est ignoré même s’il est spécifié
    • Elle doit être utilisée avec prudence
  • En-têtes d’authentification et conditions de rejeu

    • Les requêtes HTTP authentifiées incluent des en-têtes X-BlogTech-
    • X-BlogTech-Nonce : jeton choisi aléatoirement par le client
    • X-BlogTech-Timestamp : horodatage UNIX du moment où la requête a été signée pour la première fois
    • X-BlogTech-Expire : durée de validité de la requête, en secondes, à partir du moment de la signature
    • X-BlogTech-Signature : HMAC des informations de la requête, encodé en Base64
    • La signature est obtenue en construisant une chaîne de requête normalisée, puis en calculant HMAC/SHA256 avec le mot de passe d’authentification comme clé, et en encodant le résultat en Base64 avec remplissage
    • Les nonces déjà vus par le serveur sont stockés en mémoire
    • Quand le serveur est rechargé, il oublie les anciens nonces ; les requêtes non expirées peuvent donc être rejouées

Hôtes virtuels et mappage des répertoires

  • BlogTech peut héberger plusieurs sites web sur un même serveur
  • Les domaines sont configurés en spécifiant plusieurs fois l’option --domain
  • BlogTech crée des répertoires par domaine dans la racine documentaire
    • default
    • websiteA.com
    • websiteB.com
  • Une requête arrivant pour un hôte donné consulte le répertoire associé à cet hôte
  • Les requêtes qui ne sont associées à aucun dossier spécifique consultent le répertoire default
  • Dans l’exemple, les fichiers téléversés vers websiteA.com, websiteB.com et other.com sont stockés respectivement dans les répertoires de domaine ou dans le répertoire default

HTTPS et configuration des certificats

  • HTTPS n’est pris en charge que sous Linux
  • Cela tient au fait que la bibliothèque HTTP sous-jacente, cHTTP, implémente HTTPS avec OpenSSL
  • Sous Windows, le client comme le serveur ne peuvent utiliser que HTTP
  • Pour activer HTTPS, les options suivantes sont nécessaires
    • --https-enabled
    • --cert-file
    • --cert-key-file
  • L’adresse d’écoute HTTPS par défaut est 127.0.0.1:8443
  • L’adresse et le port d’écoute peuvent être modifiés avec les options suivantes
    • --https-addr=<addr>
    • --https-port=<port>
  • Pendant le développement, une commande OpenSSL permet de créer un certificat autosigné
  • Les navigateurs n’autorisent pas la consultation d’un serveur HTTPS utilisant un certificat autosigné
  • cURL doit utiliser le drapeau --insecure pour accéder à un serveur avec certificat autosigné
  • Si plusieurs certificats sont nécessaires, il est possible de passer des --extra-cert supplémentaires
    • Le certificat par défaut est fourni avec --cert-file et --cert-key-file
    • Les certificats supplémentaires sont utilisés quand le client demande un domaine donné
  • Les longues options en ligne de commande peuvent être déplacées dans un fichier de configuration

Émission automatique de certificats via ACME

  • Le protocole ACME permet à un serveur web de demander automatiquement l’émission de certificats à une autorité de certification
  • BlogTech prend en charge un flux où il démarre en mode HTTPS sans certificat, puis génère le certificat
  • En production, les conditions suivantes sont requises
    • exécuter le serveur HTTP sur le port 80
    • créer un enregistrement DNS reliant le domaine à la machine sur laquelle tourne le serveur
  • ACME s’active en spécifiant --acme-enabled avec les options HTTPS
  • Contrairement au mode HTTPS classique, le mode ACME s’attend à ce qu’aucun certificat n’existe
  • Les valeurs suivantes sont nécessaires pour générer un certificat
    • --acme-domain
    • --acme-email
    • --acme-country
    • --acme-organization
    • --acme-agree-tos
  • Si tout se passe correctement, les fichiers suivants sont générés
    • acme_key.pem : clé privée associée au compte ACME
    • cert.pem : certificat émis, ou fichier spécifié avec --cert-file
    • key.pem : clé privée associée au certificat, ou fichier spécifié avec --cert-key-file
  • En cas d’erreur, les messages sont consignés dans acme.log
  • En spécifiant plusieurs fois --acme-domain, plusieurs domaines peuvent être traités par ACME
  • Le certificat résultant inclut tous les domaines spécifiés

Test du client ACME

  • Pour tester le client ACME sous Linux, il faut installer Docker et cloner Pebble ACME server
  • Dans docker-compose.yml, ajouter une entrée extra_hosts qui associe le domaine de test à host-gateway
  • Dans /etc/hosts, mapper le domaine de test vers 127.0.0.1
  • Pebble démarre avec docker compose up
  • BlogTech lance une instance de test avec ./blogtech -s --config=misc/pebble_blogtech.conf
  • Les interactions avec le serveur ACME sont affichées sur stdout
  • En cas de succès, acme_key.pem, cert.pem et key.pem sont générés
  • Le test de renouvellement de certificat peut se faire en modifiant la valeur validityPeriod dans pebble/test/config/pebble-config.json
  • L’option --acme-force-renewal-period=<maximum duration in ms> permet d’indiquer qu’il faut renouveler le certificat même s’il n’a pas expiré

Fichiers de configuration et chargement automatique

  • BlogTech peut déplacer un nombre arbitraire d’arguments de ligne de commande dans un fichier de configuration
  • Par exemple, les options liées à HTTPS et ACME peuvent être écrites dans blogtech_server.conf, puis exécutées avec --config=blogtech_server.conf
  • Si le fichier de configuration s’appelle exactement blogtech.conf, BlogTech le charge automatiquement
  • Dans ce cas, il peut être lancé comme ./blogtech --serve
  • Pour ignorer le chargement implicite du fichier de configuration, utiliser --no-config

Journaux de crash et démon systemd

  • Si BlogTech plante en mode serveur, un fichier crash.bin est créé
  • Au démarrage suivant du serveur, crash.bin est converti en crash.log, une stack trace lisible par un humain
  • Le processus de conversion des adresses en noms de symboles ou numéros de ligne peut être quelque peu instable
  • BlogTech peut être installé comme démon systemd
  • L’exemple blogtech.service exécute /root/blogtech/blogtech -s, redémarre en cas d’échec et définit le répertoire de travail sur /root/blogtech/
  • Les options du serveur sont chargées automatiquement depuis /root/blogtech/blogtech.conf
  • Après avoir copié le fichier de service dans /etc/systemd/system/, l’activer et le démarrer avec les commandes suivantes
    • systemctl daemon-reload
    • systemctl enable blogtech
    • systemctl start blogtech
  • La gestion du service s’effectue avec systemctl start, systemctl stop, systemctl restart et journalctl -u blogtech

1 commentaires

 
GN⁺ 2024-09-26
Commentaires sur Hacker News
  • La partie « pas besoin de reverse proxy » m’a toujours intrigué
    S’il n’y avait pas de raison particulière pour qu’un reverse proxy soit utile, il m’est arrivé d’exposer directement sur Internet une appli Jetty embarquée, sans couche frontale, et ça n’a jamais posé de problème
    Les responsables infra ou sécurité demandent pourquoi ne pas mettre nginx devant, mais quand on leur demande pourquoi, ils parlent vaguement de sécurité ou de performances sans être très précis. La réponse la plus concrète que j’aie eue concernait slow loris, mais ce n’est plus vraiment un gros problème depuis longtemps
    Je me demande si le reverse proxy est devenu collectivement un cargo cult, ou si je passe à côté de bonnes raisons valables de manière générale

    • Pour moi, le reverse proxy permet au serveur d’origine de se concentrer uniquement sur la fourniture de l’application. La terminaison TLS, l’équilibrage de charge, la réécriture d’URL et, si nécessaire, des fonctions de sécurité comme un WAF peuvent toutes être gérées par le reverse proxy, ce qui crée une séparation des responsabilités
      Globalement, cela permet de protéger le serveur d’origine et de ne lui faire recevoir que le trafic pertinent. Quand on fournit un domaine personnalisé à un client, son DNS pointe aussi vers le reverse proxy et non vers le serveur d’origine, donc on peut changer ce dernier si besoin sans se soucier de devoir faire modifier le DNS du client
    • Je fais tourner plusieurs programmes serveur dans mon homelab
      Chacun écoute sur un port différent, mais je veux les rendre tous accessibles publiquement via des URL distinctes, tout en n’exposant sur Internet que le port 443
      Je veux aussi le renouvellement automatique des certificats TLS pour chaque domaine. Pour ces besoins, il faut un reverse proxy, et Caddy gère les deux
      Si on n’exploite qu’un seul serveur et qu’il s’occupe lui-même de la terminaison TLS, alors le reverse proxy n’est pas indispensable
    • Dans la plupart des déploiements, l’impact du reverse proxy sur les performances est négligeable, et la configuration est déjà prête
      Comme on peut ensuite y ajouter la terminaison TLS, la réécriture d’URL et d’autres fonctions sans grand effort, c’est devenu une habitude, et jusqu’ici cette habitude a toujours été utile
    • Pour répondre de façon plus générale, valable pour différents types de serveurs placés devant une application web, il y a plusieurs façons dont un équipement supplémentaire peut aider à la fois en sécurité et en performances
      Côté sécurité, on veut idéalement le moins de code possible sur l’ensemble du système, avec un système d’exploitation robuste. Une application de type proxy peut être bien plus simple qu’un serveur web ou applicatif, et peut aussi filtrer le trafic entrant, valider les entrées ou les transformer dans un format plus sûr et plus rapide à analyser. Elle peut aussi tourner sur des systèmes d’exploitation plus difficiles à attaquer, comme OpenBSD, GenodeOS ou INTEGRITY-178B
      Côté disponibilité, il est souvent plus sûr de placer sur ce type de système l’équilibrage de charge, la supervision et la reprise, parce que les serveurs applicatifs ont davantage tendance à tomber en panne
      Côté performances, le premier avantage vient du fait qu’une application simple et ciblée peut être fortement optimisée. Ensuite, on peut accélérer la compression ou le chiffrement via le CPU ou des accélérateurs matériels PCI, ce qu’on appelle souvent de l’offloading. La configuration la plus rentable consiste souvent à faire bénéficier de nombreux serveurs généralistes d’un petit nombre de serveurs d’offloading coûteux. Certains utilisent aussi l’équilibrage de charge pour envoyer le trafic entrant vers le serveur le plus à même de le traiter, afin de réduire l’utilisation de ressources chères
    • Je ne pense pas qu’il existe une règle générale valable pour tous les serveurs
      Dans une configuration minimale, ce n’est pas nécessaire, mais même sur un seul hôte, cela permet des rolling releases, la compression, TLS, un service rapide des fichiers statiques, et potentiellement des tests A/B
      Une couche d’indirection entre les requêtes et le serveur peut être très utile
  • Très sympa. J’ai moi aussi écrit il y a longtemps un serveur web en C, et le code source est ci-dessous. Il a même fait tourner pendant un temps un site commercial
    C’est étonnant de voir à quel point on peut rendre un serveur web HTTP/1.1 petit et léger. Ce site commercial tournait sur une machine avec 128 Mo de RAM et un seul CPU, et desservait régulièrement une bonne partie des écoles du Royaume-Uni avec un système de chat interactif fermé, basé sur le web. Bon, c’était il y a 20 ans, à une époque où Internet était moins hostile qu’aujourd’hui
    L’article disait que les bots font d’excellents fuzzers, mais je pense quand même qu’il vaut mieux faire aussi un peu de fuzzing réel
    http://git.annexia.org/?p=rws.git;a=tree
    Dépendances :
    http://git.annexia.org/?p=c2lib.git;a=tree
    http://git.annexia.org/?p=pthrlib.git;a=tree

    • Si on veut faire tourner un serveur web de cette taille sans trop se soucier de l’hostilité d’Internet, Rust est un bon choix
      Mon site https://blessed.rs tourne sur la plus petite VM que j’ai pu trouver, avec 256 Mo de RAM, mais il n’en utilise généralement qu’environ 60 Mo
    • Ça a l’air bien plus pratique que le petit serveur web HTTP/1.0 léger que j’ai écrit, mais rws ne semble pas pour autant aussi petit et léger : http://canonical.org/~kragen/sw/dev3/server.s http://canonical.org/~kragen/sw/dev3/httpdito-readme
      Ce qui m’avait vraiment impressionné, c’est qu’avec une empreinte mémoire de seulement cinq pages de 4 Ko, le fork de Linux devenait incroyablement rapide
  • C’est un petit projet commencé sur mon temps libre, pour le plaisir, et je me suis dit que ça pourrait plaire ici :)

    • Parcourir d’anciens bugs de serveurs HTTP et des CVE pour voir s’ils pouvaient aussi affecter mon code et comment les corriger est un exercice intéressant. Construire ce genre de chose soi-même, c’est amusant
    • J’avais justement envie de toucher à l’API de concurrence C11, et venant du C++, j’étais curieux de voir comment ces structures fonctionnaient en C, donc j’avais envie d’écrire quelque chose qui ressemble à un serveur
  • Impressionnant. J’ai toujours trouvé, et je le pense encore, qu’il est vraiment satisfaisant de lancer un service minimal en utilisant les API système les plus bas niveau de cette façon.
    Ça ressemble presque à de la magie, et encore plus quand on voit que ça traite du vrai trafic. J’ai été un peu surpris qu’un simple poll() puisse atteindre de tels chiffres, mais c’est sans doute parce que ça fait longtemps que je n’ai pas touché aux événements ou aux benchmarks à ce niveau
    J’aime aussi l’approche avec une fonction par connexion, les structures associées, la gestion des connexions par tableaux, ainsi que le tableau de descripteurs de fichiers pour poll. Ça rappelle la manière de faire de plusieurs paquets open source connus pour leur haut débit, comme nginx, Redis et memcached

    • À l’université, travailler avec le C/C++ me donnait l’impression que mon cerveau allait exploser. C’était une expérience très particulière et très humiliante, avec un peu de tout ce que j’aime dedans : l’ingénierie, l’histoire, la culture, la linguistique
      Ça m’a fait penser que tout le monde devrait connaître et essayer toutes les langues possibles, qu’il s’agisse de langages de programmation ou de langues naturelles. Penser dans une langue est une expérience unique. Des contextes différents donnent une sensation différente à tout, et même quand on fait au fond quelque chose de similaire, le point de vue change
      Par exemple, pour vraiment comprendre l’essence de Linux ou de Git, il faut parler cette langue et comprendre les nuances qui disparaissent généralement dans la traduction. C’est un peu comme pour saisir le véritable sens subjectif du mot « forêt » en russe : il faut parler et comprendre le russe
      Le contexte change la perspective, et parfois change tout
    • Entièrement d’accord. Et c’est encore plus satisfaisant quand on l’utilise réellement. Maintenant, je commence même à m’intéresser aux protocoles d’e-mail
      Moi aussi, j’ai été surpris de voir qu’un simple poll() tenait aussi bien. Je pensais qu’on finirait un jour par passer à epoll, mais poll() fonctionne très bien
    • On dirait que les gens oublient que toutes les abstractions formidables et élégantes font au fond exactement la même chose. Elles ouvrent des sockets, lisent et écrivent
      Il n’y a rien de nouveau sous le soleil
  • Ça peut aussi être intéressant : https://news.ycombinator.com/item?id=27431910
    En 2024, l’instance althttpd de sqlite.org traite plus de 500 000 requêtes HTTP par jour, soit environ 5 à 6 par seconde, et sert environ 200 Go de contenu par jour, soit environ 18 Mbit/s, sur un Linode à 40 dollars par mois. La charge moyenne de cette machine reste généralement autour de 0,5. Environ 19 % des requêtes HTTP sont des CGI destinés à plusieurs dépôts de code source Fossil

    • Cet article m’a énormément inspiré. Il m’a fait réaliser que ce genre de chose est réellement possible
  • Si vous voulez écrire une application en C mais que vous n’êtes pas à l’aise à l’idée de coder vous-même la partie directement exposée à l’internet public, Kore est un framework intéressant
    Il intègre des fonctionnalités pratiques comme la gestion des certificats ACME, Pgsql, curl et les WebSocket
    Il permet essentiellement de construire, exécuter et composer des modules, et il est aussi possible de mélanger Lua/Python et C
    https://kore.io/

  • Enfin un site web qui ne tombe pas même après être arrivé en première page

    • Avec un CDN devant, n’importe quel site peut faire ça
      Ça ne veut pas dire que ce projet n’est pas excellent, mais si c’est vraiment important pour vous en production et que vous servez surtout du contenu statique, il suffit d’utiliser un CDN. Il offrira toujours de meilleures performances que presque tout ce que vous pourriez écrire vous-même. C’est juste moins amusant
    • Le lien ne pointe-t-il pas vers GitHub ? Ce commentaire me perturbe un peu
      Le projet est propre et élégant, mais j’ai l’impression que la plupart des gens iront sur GitHub plutôt que sur un lien montrant la page web. Est-ce que j’ai raté quelque chose ?
  • J’aime bien le passage : « J’aime fabriquer mes propres outils, et j’en ai un peu assez d’entendre qu’il faut que tout soit éprouvé en production. Alors, et si ça casse ? On corrigera les bugs :^) »

  • Un serveur HTTP et HTTPS complet en seulement 3,4 k lignes de code C ? Honnêtement, je pensais qu’il en faudrait bien plus pour respecter complètement les spécifications

    • HTTP/1.1 est très simple si on ignore l’essentiel de la spécification
      Accepter uniquement les requêtes GET et définir Content-Length dans la réponse suffit pour 99 % des agents utilisateurs. Gérer Transfer-Encoding et les en-têtes de plage d’octets n’augmente pas énormément la taille du code
      HTTPS, c’est juste du HTTP au-dessus d’un socket TLS, et si vous n’implémentez pas vous-même le chiffrement, ce devrait être exactement ce niveau d’abstraction. C’est amusant et, en pratique, ce n’est pas si mauvais
    • Ça me paraît raisonnable. Le httpd(8) d’OpenBSD [1] fait lui aussi actuellement un peu moins de 15 000 lignes, documentation comprise
      En retirant quelques fonctionnalités et en posant quelques hypothèses, il n’est pas surprenant qu’un projet comme celui-ci tienne dans les 5 000 lignes
      Le résultat de $ wc -l * donne un total de 14815 lignes
      [1]: https://man.openbsd.org/httpd.8
    • Dans l’une de mes expériences, j’avais utilisé un petit serveur web C embarqué pour fournir une vue temps réel de collecte de données, et il faisait moins de 250 lignes
      Bien sûr, je ne le mettrais pas sur l’internet public et il n’implémentait qu’une toute petite partie de HTTP/1.1, mais ça fonctionne et malloc n’est nécessaire qu’à l’initialisation
    • Il existe quelques autres serveurs HTTP/1.1 de cette taille : https://www.acme.com/software/thttpd/benchmarks.html
  • Ça me rappelle une présentation au Chaos Communication Congress sur un blog/serveur web écrit en C
    Elle couvrait beaucoup de fonctions de sécurité, comme le stockage immuable, la réduction de privilèges et le fait d’empêcher le blog d’accéder aux certificats TLS : https://www.youtube.com/watch?v=TaE28fJVPTk