Auf HN vorgestellt: Hosting von Websites mit einem C-Webserver
(github.com/cozis)- BlogTech ist ein C-basiertes Toolkit zur Verwaltung kleiner bis mittelgroßer Websites und bietet HTTPS, virtuelle Hosts, automatische Zertifikatsverwaltung auf ACME-Basis sowie einen Client zur Fernverwaltung von Servern
- Es befindet sich derzeit in der Testphase; Version
0.4.xwurde zwar einige Wochen im Betrieb genutzt, wird aber nicht als stabiles Release präsentiert - Der Server läuft unter Linux und Windows, aber HTTPS wird nur unter Linux unterstützt; unter Windows können sowohl Client als auch Server nur HTTP verwenden
- Die Remote-Dateiverwaltung erfolgt über
--put,--get,--delete, und Anfragen, die Ressourcen verändern, werden mit HMAC/SHA256-Signaturen auf Basis eines gemeinsamen Geheimschlüssels authentifiziert - In Produktionsumgebungen werden ein HTTP-Server auf Port 80 und DNS-Einträge für die Domain benötigt; wenn ACME aktiviert ist, werden Zertifikate und private Schlüsseldateien automatisch erzeugt und Fehler in
acme.logprotokolliert
Zweck und aktueller Status von BlogTech
- BlogTech ist ein Toolkit zur Verwaltung kleiner bis mittelgroßer Websites
- Unterstützte Funktionen sind:
-
HTTPS
-
Virtuelle Hosts
- Automatische Zertifikatsverwaltung über ACME
- Client zur Fernverwaltung des Servers
- Noch in der Testphase
- Mit Version
0.4.xwurdehttps://coz.is/einige Wochen lang erfolgreich betrieben - Eine ältere Single-File-Version befindet sich im Branch
single_file
-
Build und grundlegende Ausführung
- BlogTech läuft unter Linux und Windows
- Für den Build unter Linux werden die OpenSSL-Entwicklungsbibliotheken und
gccbenötigt - Für den Build unter Windows wird
clangbenötigt
- Für den Build unter Linux werden die OpenSSL-Entwicklungsbibliotheken und
- Der Build erfolgt über plattformspezifische Skripte
- Linux:
./build.sh - Windows:
.\build.bat
- Linux:
- Die Build-Artefakte sind:
- Linux:
blogtech - Windows:
blogtech.exe
- Linux:
- Unter Linux kann die Installation mit
./install.sherfolgen - Ein Beispiel für den Start des Basis-Servers erstellt ein Verzeichnis
docrootund setzt--serve,--document-root=docroot,--skip-auth-check - Der Standard-HTTP-Server lauscht auf
127.0.0.1:8080und liefert die Inhalte ausdocrootaus - Wenn
docrootleer ist, wird beim Aufruf vonhttp://127.0.0.1:8080/ein 404 zurückgegeben
Remote-Dateiverwaltung und Authentifizierung
- BlogTech kann im Client-Modus Dateien auf den Server hochladen
- Datei-Upload mit
--put - Herunterladen entfernter Dateien mit
--get - Löschen entfernter Dateien mit
--delete
- Datei-Upload mit
- Auch wenn Client und Server auf derselben Maschine laufen, funktioniert dies auf dieselbe Weise für Server auf entfernten Maschinen
- Anfragen, die Ressourcen verändern, werden digital mit HMAC/SHA256-Signaturen signiert
- Client und Server müssen denselben geheimen Schlüssel teilen, der üblicherweise in der Datei
admin.pwdgespeichert wird - Die Passwortdatei wird mit der Option
--auth-password-fileangegeben - Wenn keine Passwortdatei vorhanden ist, werden alle authentifizierungspflichtigen Anfragen abgelehnt
- Ein leeres Passwort wird ebenfalls abgelehnt
- Während der Entwicklung kann mit
--skip-auth-checkjede Anfrage als authentifiziert behandelt werden- Bei Verwendung dieser Option wird
--auth-password-fileauch dann ignoriert, wenn es angegeben ist - Es sollte mit Vorsicht verwendet werden
- Bei Verwendung dieser Option wird
-
Authentifizierungs-Header und Bedingungen für Replay
- Authentifizierte HTTP-Anfragen enthalten
X-BlogTech--Header X-BlogTech-Nonce: Ein vom Client zufällig gewähltes TokenX-BlogTech-Timestamp: UNIX-Zeitstempel des Zeitpunkts, an dem die Anfrage zuerst signiert wurdeX-BlogTech-Expire: Gültigkeitsdauer der Anfrage in Sekunden ab dem SignaturzeitpunktX-BlogTech-Signature: Base64-kodierter HMAC der Anfrageinformationen- Die Signatur wird erzeugt, indem zunächst ein normalisierter Request-String erstellt, dann mit dem Authentifizierungspasswort als Schlüssel HMAC/SHA256 berechnet und das Ergebnis anschließend mit Padding Base64-kodiert wird
- Nonces, die der Server bereits gesehen hat, werden im Speicher gehalten
- Wenn der Server neu geladen wird, vergisst er frühere Nonces; noch nicht abgelaufene Anfragen können daher erneut abgespielt werden
- Authentifizierte HTTP-Anfragen enthalten
Virtuelle Hosts und Verzeichniszuordnung
- BlogTech kann mehrere Websites auf demselben Server hosten
- Domains werden durch mehrfaches Angeben der Option
--domainkonfiguriert - BlogTech erstellt innerhalb des Document-Roots domainspezifische Verzeichnisse
defaultwebsiteA.comwebsiteB.com
- Anfragen an einen bestimmten Host greifen auf das mit diesem Host verknüpfte Verzeichnis zu
- Anfragen, die keinem bestimmten Ordner zugeordnet sind, verwenden das Verzeichnis default
- Im Beispiel werden Dateien, die auf
websiteA.com,websiteB.comundother.comhochgeladen werden, jeweils im Domain-Verzeichnis oder im Verzeichnisdefaultgespeichert
HTTPS- und Zertifikatskonfiguration
- HTTPS wird nur unter Linux unterstützt
- Der zugrunde liegende HTTP-Stack cHTTP implementiert HTTPS mit OpenSSL
- Unter Windows können sowohl Client als auch Server nur HTTP verwenden
- Zum Aktivieren von HTTPS werden folgende Optionen benötigt
--https-enabled--cert-file--cert-key-file
- Die Standard-Adresse für HTTPS ist
127.0.0.1:8443 - Adresse und Port können mit den folgenden Optionen geändert werden
--https-addr=<addr>--https-port=<port>
- Für die Entwicklung kann mit einem OpenSSL-Befehl ein selbstsigniertes Zertifikat erzeugt werden
- Browser erlauben kein normales Navigieren zu HTTPS-Servern mit selbstsignierten Zertifikaten
- Bei cURL muss das Flag
--insecureverwendet werden, um auf einen Server mit selbstsigniertem Zertifikat zuzugreifen - Wenn mehrere Zertifikate benötigt werden, kann zusätzlich
--extra-certübergeben werden- Das Standardzertifikat wird mit
--cert-file,--cert-key-filebereitgestellt - Zusätzliche Zertifikate werden verwendet, wenn ein Client eine bestimmte Domain anfordert
- Das Standardzertifikat wird mit
- Lange Kommandozeilenoptionen können in eine Konfigurationsdatei ausgelagert werden
Automatische Zertifikatsausstellung auf ACME-Basis
- Das ACME-Protokoll ermöglicht es einem Webserver, automatisch bei einer Zertifizierungsstelle die Ausstellung eines Zertifikats anzufordern
- BlogTech unterstützt einen Ablauf, bei dem der HTTPS-Modus ohne Zertifikat gestartet wird und das Zertifikat anschließend erzeugt wird
- In Produktionsumgebungen sind folgende Bedingungen erforderlich
- Der HTTP-Server läuft auf Port 80
- Es existieren DNS-Einträge, die die Domain mit der Maschine verbinden, auf der der Server läuft
- ACME wird mit
--acme-enabledzusammen mit den HTTPS-Optionen aktiviert - Anders als im normalen HTTPS-Modus wird im ACME-Modus erwartet, dass noch kein Zertifikat vorhanden ist
- Für die Zertifikatserstellung werden folgende Werte benötigt
--acme-domain--acme-email--acme-country--acme-organization--acme-agree-tos
- Bei erfolgreicher Verarbeitung werden folgende Dateien erzeugt
acme_key.pem: Geheimer Schlüssel des ACME-Kontoscert.pem: Ausgestelltes Zertifikat oder die mit--cert-fileangegebene Dateikey.pem: Privater Schlüssel des Zertifikats oder die mit--cert-key-fileangegebene Datei
- Bei Fehlern werden Meldungen in
acme.logprotokolliert - Wenn
--acme-domainmehrfach angegeben wird, können mehrere Domains per ACME verarbeitet werden - Das resultierende Zertifikat enthält alle angegebenen Domains
Test des ACME-Clients
- Um den ACME-Client unter Linux zu testen, müssen Docker installiert und der Pebble ACME server geklont werden
- In
docker-compose.ymlwird einextra_hosts-Eintrag hinzugefügt, der die Test-Domain aufhost-gatewayabbildet - In
/etc/hostswird die Test-Domain auf127.0.0.1gemappt - Pebble wird mit
docker compose upgestartet - BlogTech startet die Testinstanz mit
./blogtech -s --config=misc/pebble_blogtech.conf - Die Interaktion mit dem ACME-Server wird auf stdout ausgegeben
- Bei Erfolg werden
acme_key.pem,cert.pem,key.pemerzeugt - Ein Test der Zertifikatserneuerung kann durchgeführt werden, indem der Wert
validityPeriodinpebble/test/config/pebble-config.jsongeändert wird - Mit der Option
--acme-force-renewal-period=<maximum duration in ms>kann angewiesen werden, das Zertifikat auch dann zu erneuern, wenn es noch nicht abgelaufen ist
Konfigurationsdatei und automatisches Laden
- BlogTech kann eine beliebige Anzahl von Kommandozeilenargumenten in eine Konfigurationsdatei auslagern
- Beispielsweise können HTTPS- und ACME-bezogene Optionen in
blogtech_server.confgeschrieben und dann mit--config=blogtech_server.confverwendet werden - Wenn die Konfigurationsdatei exakt
blogtech.confheißt, lädt BlogTech sie automatisch - In diesem Fall kann es etwa mit
./blogtech --servegestartet werden - Um das implizite Laden der Konfigurationsdatei zu ignorieren, wird
--no-configverwendet
Crash-Logs und systemd-Daemon
- Wenn BlogTech im Server-Modus abstürzt, wird eine Datei
crash.binerzeugt - Beim nächsten Serverstart wird
crash.binincrash.logumgewandelt, einen für Menschen lesbaren Stack-Trace - Die Umwandlung von Adressen in Symbolnamen oder Zeilennummern kann etwas unzuverlässig sein
- BlogTech kann als systemd-Daemon installiert werden
- Die Beispiel-Unit
blogtech.serviceführt/root/blogtech/blogtech -saus, startet bei Fehlern neu und setzt das Arbeitsverzeichnis auf/root/blogtech/ - Serveroptionen werden automatisch aus
/root/blogtech/blogtech.confgeladen - Nachdem die Service-Datei nach
/etc/systemd/system/kopiert wurde, wird sie mit den folgenden Befehlen aktiviert und gestartetsystemctl daemon-reloadsystemctl enable blogtechsystemctl start blogtech
- Die Verwaltung des Dienstes erfolgt mit
systemctl start,systemctl stop,systemctl restart,journalctl -u blogtech
1 Kommentare
Meinungen auf Hacker News
Der Teil „Reverse Proxy nicht nötig“ hat mich schon immer verwundert.
Wenn es keinen besonderen Grund gab, warum ein Reverse Proxy hilfreich wäre, habe ich eingebettete Jetty-Apps oft ohne vorgeschaltete Komponente direkt ins Internet gestellt, und es gab keine Probleme.
Infrastruktur- oder Sicherheitsverantwortliche fragen dann, warum man kein nginx davor setzt, aber wenn man nach dem Grund fragt, nennen sie nur vage Sicherheit oder Performance, ohne konkret zu werden. Die konkreteste Antwort war Slowloris, aber das ist schon lange kein großes Problem mehr.
Ich frage mich, ob Reverse Proxys kollektiv zu einem Cargo Cult geworden sind oder ob ich einen guten, allgemein gültigen Grund übersehe.
Insgesamt kann man so den Ursprungsserver schützen und dafür sorgen, dass er nur relevanten Traffic erhält. Auch wenn man Kunden Custom Domains anbietet, zeigt deren DNS auf den Reverse Proxy und nicht auf den Ursprungsserver; wenn nötig, kann man den Ursprungsserver austauschen, ohne sich um DNS-Änderungen bei Kunden kümmern zu müssen.
Sie laufen jeweils auf unterschiedlichen Ports, aber ich möchte sie alle über verschiedene URLs öffentlich erreichbar machen und im Internet nur Port 443 freigeben.
Außerdem möchte ich die TLS-Zertifikate für jede Domain automatisch erneuern. Für die erste Anforderung braucht man einen Reverse Proxy, und Caddy erledigt beides.
Wenn man nur einen einzigen Server betreibt und dieser auch die TLS-Terminierung übernimmt, ist ein Reverse Proxy nicht zwingend nötig.
Später lassen sich TLS-Terminierung, URL-Rewriting und andere Aufgaben ohne großen Aufwand hinzufügen, deshalb habe ich mir die Nutzung angewöhnt, und bisher hat sich diese Gewohnheit ausgezahlt.
Aus Sicherheitssicht möchte man im Gesamtsystem möglichst wenig Code und ein robustes Betriebssystem einsetzen. Eine Proxy-artige App kann deutlich einfacher sein als ein Web-/Anwendungsserver; sie kann eingehenden Traffic filtern, Eingaben validieren oder sie in eine Form umwandeln, die sicherer und schneller zu parsen ist. Man kann sie auch auf schwer angreifbaren Betriebssystemen wie OpenBSD, GenodeOS oder INTEGRITY-178B laufen lassen.
Auch aus Verfügbarkeitssicht ist es oft sicherer, Load Balancing, Monitoring und Recovery in solchen Systemen unterzubringen, weil Anwendungsserver häufiger ausfallen können.
Aus Performance-Sicht besteht der erste Vorteil darin, dass eine einfache, fokussierte App stark optimiert werden kann. Danach lassen sich Komprimierung oder Verschlüsselung per CPU- oder PCI-Hardwarebeschleuniger beschleunigen; das nennt man oft Offloading. Die kosteneffizienteste Konfiguration ist eine, bei der viele Allzweckserver von wenigen teuren Offloading-Servern profitieren. Manche leiten eingehenden Traffic per Load Balancing an den Server weiter, der ihn am besten verarbeiten kann, und reduzieren so den Einsatz teurer Ressourcen.
In einer Minimal-Konfiguration braucht man es nicht, aber selbst in einer Single-Host-Betriebsumgebung ermöglicht es Rolling Releases, Komprimierung, TLS, schnelle Auslieferung statischer Dateien und potenzielle A/B-Tests.
Eine indirekte Schicht zwischen Request und Server kann ziemlich nützlich sein.
Cool. Ich habe früher auch selbst einen C-Webserver geschrieben; der Quellcode ist unten. Eine Zeit lang habe ich damit sogar eine kommerzielle Website betrieben.
Es ist erstaunlich, wie klein und leichtgewichtig man einen HTTP/1.1-Webserver machen kann. Diese kommerzielle Site lief auf einer Maschine mit 128 MB RAM und einer CPU und bediente als Closed-Source, interaktives webbasiertes Chatsystem regelmäßig einen beträchtlichen Teil der Schulen im Vereinigten Königreich. Allerdings war das vor 20 Jahren, als das Internet weniger feindselig war als heute.
Im Beitrag heißt es, Bots seien großartige Fuzzer, aber ich denke trotzdem, dass man auch ein wenig echtes Fuzzing machen sollte.
http://git.annexia.org/?p=rws.git;a=tree
Benötigt:
http://git.annexia.org/?p=c2lib.git;a=tree
http://git.annexia.org/?p=pthrlib.git;a=tree
Meine Website https://blessed.rs läuft auf der kleinsten VM, die ich finden konnte, mit 256 MB RAM, verwendet aber normalerweise nur etwa 60 MB.
Wirklich überraschend war, dass Linux-fork enorm schnell wird, wenn die Memory Map nur aus fünf 4-KB-Seiten besteht.
Das ist ein kleines Projekt, das ich zum Spaß in meiner Freizeit begonnen habe, und ich dachte, es könnte hier gefallen :)
Großartig. Ich fand es schon immer und finde es immer noch sehr befriedigend, auf diese Weise mit den niedrigsten System-APIs einen minimalen Dienst hochzufahren
Es wirkt fast magisch, erst recht, wenn man sieht, dass er echten Traffic verarbeitet. Dass ein ganz normales
poll()solche Werte liefern kann, hat mich etwas überrascht, aber ich habe schon lange nicht mehr auf dieser Ebene mit Events oder Benchmarks gearbeitetMir gefällt auch die Verwaltung der Verbindungen über Funktionen pro Verbindung und zugehörige Structs und Arrays, ebenso das Array der
poll-Dateideskriptoren. Das erinnert an die Vorgehensweise vieler Open-Source-Pakete, die für hohen Durchsatz bekannt sind, wie nginx, Redis oder memcachedDas brachte mich zu der Ansicht, dass jeder alle möglichen Sprachen, ob Programmiersprachen oder natürliche Sprachen, kennen und ausprobieren sollte. In einer bestimmten Sprache zu denken ist eine einzigartige Erfahrung. Unterschiedliche Kontexte lassen alles anders wirken, und selbst wenn man am Ende Ähnliches tut, ändert sich die Perspektive
Um zum Beispiel das Wesen von Linux oder Git wirklich zu verstehen, muss man diese Sprache sprechen und die Nuancen verstehen, die in der Übersetzung normalerweise verloren gehen. Ähnlich wie man Russisch sprechen und verstehen muss, um die wirkliche subjektive Bedeutung des russischen Wortes für „Wald“ zu begreifen
Kontext verändert die Perspektive, und manchmal verändert er alles
Ich war ebenfalls überrascht, dass ein gewöhnliches
poll()so viel aushält. Ich dachte, ich würde irgendwann aufepollumsteigen, aberpoll()funktioniert ausgesprochen gutEs gibt nichts Neues unter der Sonne
Das hier könnte ebenfalls interessant sein: https://news.ycombinator.com/item?id=27431910
Stand 2024 verarbeitet die althttpd-Instanz von sqlite.org mehr als 500.000 HTTP-Requests pro Tag, etwa 5 bis 6 pro Sekunde, und liefert pro Tag rund 200 GB Content, etwa 18 Mbit/s, von einem Linode für 40 Dollar im Monat aus. Der Load Average dieser Maschine bleibt normalerweise in der Nähe von 0,5. Etwa 19 % der HTTP-Requests sind CGI-Aufrufe an mehrere Fossil-Quellcode-Repositories
Wenn man eine C-App schreiben möchte, sich aber unwohl dabei fühlt, den Teil, der direkt am öffentlichen Internet hängt, selbst zu schreiben, ist Kore ein brauchbares Framework
Es hat praktische eingebaute Funktionen wie ACME-Zertifikatsverwaltung, Pgsql, curl und WebSockets
Grundsätzlich kann man Module bauen, ausführen und kombinieren; außerdem lassen sich Lua/Python und C mischen
https://kore.io/
Endlich eine Website, die nicht stirbt, wenn sie auf die Startseite kommt
Das soll nicht heißen, dass dieses Projekt nicht großartig wäre, aber wenn einem das in der Produktion wirklich wichtig ist und man überwiegend statischen Content ausliefert, sollte man einfach ein CDN verwenden. Das wird praktisch immer besser performen als fast alles, was man selbst schreibt. Es macht nur weniger Spaß
Das Projekt ist sauber und cool, aber ich vermute, die meisten werden zu GitHub gehen und nicht zu einem Link, der die Webseite anzeigt. Übersehe ich etwas?
Mir gefällt die Stelle: „Ich habe Spaß daran, meine eigenen Tools zu bauen, und bin es etwas leid zu hören, dass alles praxiserprobt sein muss. Na und, wenn es abstürzt? Bugs kann man beheben :^)“
Ein vollständiger HTTP- und HTTPS-Server in nur 3,4k Zeilen C-Code? Um die Spezifikation vollständig einzuhalten, hätte ich ehrlich gesagt mit deutlich mehr gerechnet
Wenn man nur GET-Requests annimmt und in der Antwort
Content-Lengthsetzt, reicht das für 99 % der User Agents. Auch der Umgang mitTransfer-Encodingund Byte-Range-Headern lässt den Code nicht massiv anwachsenHTTPS ist nur HTTP über einem TLS-Socket, und wenn man die Verschlüsselung nicht selbst implementiert, sollte genau das die Abstraktionsebene sein. Interessant und in der Praxis gar nicht so schlimm
httpd(8)[1] hat aktuell, inklusive Dokumentation, auch etwas weniger als 15.000 ZeilenWenn man ein paar Features weglässt und einige Annahmen trifft, wäre es nicht überraschend, wie dieses Projekt in den Bereich von 5.000 Zeilen zu kommen
Das Ergebnis von
$ wc -l *sind insgesamt 14815 Zeilen[1]: https://man.openbsd.org/httpd.8
Natürlich würde ich ihn nicht ins öffentliche Internet stellen und er implementierte nur einen sehr kleinen Teil von HTTP/1.1, aber er funktioniert und benötigt
mallocnur bei der InitialisierungDas erinnert mich an einen Vortrag beim Chaos Communication Congress über einen in C geschriebenen Blog-/Webserver
Darin ging es um viele Sicherheitsfunktionen wie unveränderlichen Speicher, Privilege Dropping und darum, zu verhindern, dass der Blog Zugriff auf TLS-Zertifikate bekommt: https://www.youtube.com/watch?v=TaE28fJVPTk