Show HN : héberger un site web avec un serveur web en C
(github.com/cozis)- 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.xet a été utilisé pour exploiterhttps://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,--getet--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 version0.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 Linux nécessite les bibliothèques de développement OpenSSL et
- La compilation s’effectue avec des scripts propres à chaque plateforme
- Linux :
./build.sh - Windows :
.\build.bat
- Linux :
- Les artefacts de compilation sont les suivants
- Linux :
blogtech - Windows :
blogtech.exe
- Linux :
- Sous Linux, l’installation est possible avec
./install.sh - Un exemple d’exécution de serveur de base consiste à créer un répertoire
docrootet à spécifier--serve,--document-root=docrootet--skip-auth-check - Le serveur HTTP de base écoute sur
127.0.0.1:8080et sert le contenu dedocroot - Si
docrootest 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
- téléversement de fichier avec
- 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-checkpermet de traiter toutes les requêtes comme authentifiées- Avec cette option,
--auth-password-fileest ignoré même s’il est spécifié - Elle doit être utilisée avec prudence
- Avec cette option,
-
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 clientX-BlogTech-Timestamp: horodatage UNIX du moment où la requête a été signée pour la première foisX-BlogTech-Expire: durée de validité de la requête, en secondes, à partir du moment de la signatureX-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
- Les requêtes HTTP authentifiées incluent des en-têtes
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
defaultwebsiteA.comwebsiteB.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.cometother.comsont stockés respectivement dans les répertoires de domaine ou dans le répertoiredefault
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
--insecurepour accéder à un serveur avec certificat autosigné - Si plusieurs certificats sont nécessaires, il est possible de passer des
--extra-certsupplémentaires- Le certificat par défaut est fourni avec
--cert-fileet--cert-key-file - Les certificats supplémentaires sont utilisés quand le client demande un domaine donné
- Le certificat par défaut est fourni avec
- 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-enabledavec 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 ACMEcert.pem: certificat émis, ou fichier spécifié avec--cert-filekey.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éeextra_hostsqui associe le domaine de test àhost-gateway - Dans
/etc/hosts, mapper le domaine de test vers127.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.pemetkey.pemsont générés - Le test de renouvellement de certificat peut se faire en modifiant la valeur
validityPerioddanspebble/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.binest créé - Au démarrage suivant du serveur,
crash.binest converti encrash.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.serviceexé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 suivantessystemctl daemon-reloadsystemctl enable blogtechsystemctl start blogtech
- La gestion du service s’effectue avec
systemctl start,systemctl stop,systemctl restartetjournalctl -u blogtech
1 commentaires
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
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
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
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
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
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
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
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 :)
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 niveauJ’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Ç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
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, maispoll()fonctionne très bienIl 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
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
Ç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 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
Accepter uniquement les requêtes GET et définir
Content-Lengthdans la réponse suffit pour 99 % des agents utilisateurs. GérerTransfer-Encodinget les en-têtes de plage d’octets n’augmente pas énormément la taille du codeHTTPS, 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
httpd(8)d’OpenBSD [1] fait lui aussi actuellement un peu moins de 15 000 lignes, documentation compriseEn 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
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
mallocn’est nécessaire qu’à l’initialisationÇ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