urMQTT: Wir bauen einen MQTT-Broker, der zugleich das Betriebssystem ist
Entwicklungsnotizen zu urMQTT, einem in Rust geschriebenen Unikernel, der selbst der MQTT-Broker ist: ein einziges ELF mit eigenem TCP/IP, TLS 1.3, Broker und SQLite, gestartet von einem einzigen ISO.
Geräte in Fahrzeugen, auf Parkplätzen und in Schaltschränken haben eines gemeinsam: Sie senden kleine Nachrichten, selten, aber es sind viele, und sie sind ständig verbunden. Seit Jahren binden wir sie an MQTT-Broker an, und seit Jahren beschäftigt uns dieselbe Frage. Warum brauchen wir für etwas so Einfaches ein komplettes Linux, OpenSSL, die Laufzeitumgebung einer Sprache, SSH, einen Paketmanager und erst dann den Broker? Jede dieser Schichten bringt eigene Schwachstellen mit, verbraucht eigenen Speicher und fügt eine weitere Sache hinzu, die kaputtgehen kann, während eine Geräteflotte vom Server nur drei Dinge braucht: viele nahezu inaktive Verbindungen halten, jedes Gerät zuverlässig authentifizieren und Nachrichten verlustfrei weiterleiten.
So ist urMQTT entstanden: ein Unikernel, der selbst der MQTT-Broker ist. Kernel, TCP/IP, TLS 1.3, MQTT und eine eingebettete Datenbank in einem einzigen Rust-Binärprogramm, das von einem einzigen ISO gestartet wird. Es gibt keine Prozesse, keine Systemaufrufe, keine Shell. Die erste Plattform, auf der es gebaut und getestet wird, ist aarch64, und derselbe Code geht weiter in Richtung x86_64, über UEFI und klassisches BIOS, und in Richtung echter Hardware: Das Ziel ist ein einziges ISO, das auf all dem startet. Das Projekt ist Open Source unter der Lizenz MIT oder Apache 2.0, und in diesem Text möchten wir festhalten, wo wir stehen, wie es im Inneren aufgebaut ist und was wir unterwegs gelernt haben.
Was "Unikernel" genau bedeutet
Ein üblicher Broker ist ein Programm, das auf einem Betriebssystem läuft. urMQTT hat kein Betriebssystem unter sich: Es startet sich selbst über die UEFI-Firmware und den Limine-Bootloader, richtet eigene Seitentabellen ein, programmiert den Interrupt-Controller und den Timer, findet virtio-Geräte über PCIe, bezieht per DHCP eine Adresse und lauscht auf einem einzigen Port. Alles, was sonst Kernel, Treiber, Netzwerkstack und Anwendung leisten, ist hier ein einziges ELF, das auf der Ebene EL1 läuft.
Der Vergleich mit einer typischen Installation spricht für sich:
| Typischer Broker unter Linux | urMQTT | |
|---|---|---|
| Exponierte Dienste | SSH, Broker, oft auch ein Port für Metriken oder Administration | nur TCP 8883 (MQTT über TLS 1.3 mit mTLS) |
| Code im Image | Kernel, Userland, libc, OpenSSL, Laufzeitumgebung | ein einziges Rust-ELF (plus gevendortes SQLite) |
| Fernverwaltung | Shell, Konfigurationsdateien, Paketupdates | keine. Eine neue Version ist ein neues ISO |
| Speicher pro inaktiver Verbindung | Kilobytes (Socket-Puffer, TLS-Objekte, Heap) | Ziel ≤ 512 B |
| Mehrkernmodell | Threads und Sperren oder eine einzelne Ereignisschleife | ein Thread pro Kern, kein gemeinsamer Zustand, Run-to-Completion |
| Code-Integrität | Mappings, die zugleich beschreibbar und ausführbar sind, sind möglich | W^X, gesperrte Seitentabellen, PAC/BTI |
Die Prioritäten lauten in dieser Reihenfolge: Sicherheit, Skalierbarkeit, Mehrkernleistung, geringer Footprint und unveränderliche Installation. Stehen zwei Ziele im Konflikt, gewinnt dasjenige, das in dieser Liste weiter oben steht.
Wo wir jetzt stehen
Das Projekt befindet sich in der Pre-Alpha-Phase, aber siebzehn Entwicklungsmeilensteine, von M0 bis M17, sind abgeschlossen. Heute bedient urMQTT MQTT 3.1.1 über TLS 1.3 mit obligatorischen Client-Zertifikaten und hält den Zustand in SQLite auf einer rohen Festplatte. Kurz zusammengefasst, was bereits funktioniert:
- Kernel: eigene Ausnahmevektoren mit dekodierten Berichten, eigene Seitentabellen mit durchgesetztem W^X, Guard-Pages, PAC und BTI, wo der Prozessor sie besitzt, GICv3 und GICv2, generischer Timer, SMP bis zu 64 Kernen.
- Hardware-Erkennung aus DTB oder ACPI in einer einzigen Plattformbeschreibung, mit identischem Ergebnis aus beiden Quellen.
- Treiber: PCIe ECAM, MSI-X, virtio-net, virtio-blk, virtio-scsi und virtio-rng sowie ein CSPRNG, der mit Hardware-Entropie initialisiert wird.
- Netzwerk: eigenes DHCPv4, ARP, ICMP und striktes IPv4, dazu ein eigenes TCP nur für die Serverseite: SYN-Cookies, hierarchisches Timer-Rad, NewReno, Zusammenfassen von ACKs, kein TIME_WAIT-Zustand.
- TLS: nur TLS 1.3, obligatorisches mTLS, Geräteidentität aus CN oder SAN-URI; der Handshake läuft über rustls, danach folgt eine eigene Record-Schicht mit AES-128-GCM und ChaCha20-Poly1305. Sitzungswiederaufnahme mit PSK-Tickets, Sperrliste, Admission Control bereits beim Handshake.
- MQTT: Kopierfreier Codec, gefuzzt und mit echten mosquitto-Clients getestet; QoS 0 und 1, Wildcards, Retained Messages, Last Will (LWT), persistente Sitzungen, ACL mit Beschränkung auf
devices/{id}/#, Ratenbegrenzung pro Gerät. - Speicherung: SQLite im Kernel über ein VFS auf der rohen Festplatte, im WAL-Modus, mit Integritätsprüfung beim Start; Geräteregister, Sperrungen, Retained Messages, persistente Sitzungen mit Nachrichtenwarteschlangen für Geräte, die offline sind; pro Kern ein eigenes Telemetrie-Journal mit CRC je Eintrag und Wiederherstellung nach einem Absturz.
- Betrieb und Überwachung: Watchdog-Timer für Handler, Health-Check, begrenztes Logging, Syslog und statsd über UDP, eine Admission Control, die Wiederverbindungsstürme übersteht.
- Auslieferung: bitweise identische Rebuilds, die in der CI geprüft werden, ISO und rohes GPT-Festplattenabbild, minisign-Signaturen, Anleitungen für KVM, Proxmox und Hetzner.
Jeder Schritt ist mit dem Meilenstein gekennzeichnet, der ihn gebracht hat, und die Datei docs/PROGRESS.md im Repository ist die einzige Quelle der Wahrheit darüber, was tatsächlich funktioniert.
Die Architektur in drei Bildern
Das gesamte Design ist in docs/ARCHITECTURE.md beschrieben, und die Gründe für jede wichtigere Entscheidung sind als ADRs in docs/DECISIONS.md festgehalten. Hier ein Überblick.
Schichten
Von oben nach unten: Broker (Sitzungen, Topic-Baum, QoS, Retained Messages, Last Wills, ACL, Limits), MQTT-Codec, TLS, TCP, Netzwerkschicht und schließlich die Treiber. Neben diesem Hauptpfad steht die Speicherung (SQLite und Telemetrie-Journal), und unter allem liegen Speicherverwaltung, Plattformerkennung und eine Konfiguration, die zur Build-Zeit in Konstanten übersetzt wird, zusammen mit dem eingebetteten CA-Zertifikat, dem Serverzertifikat und dem Schlüssel. Der Hypervisor mit den virtio-Geräten ist das Einzige, was außerhalb des Images liegt.
Ein Thread pro Kern, kein gemeinsamer Zustand
Jeder Datenkern dreht seine eigene Schleife mit eigenem TCP-Stack, eigenen TLS-Zuständen, einem eigenen Teil des Brokers (Sitzungen und Topic-Baum), einem Heap-Cache und einer Telemetrie-Partition. Die Kerne teilen keinerlei veränderlichen Zustand, sondern nur begrenzte sperrfreie Ringe, je einen für jedes geordnete Kernpaar. Der höchste Kern führt SQLite aus und sonst nichts.
Die Netzwerkkarte hat heute ein Warteschlangenpaar, das Kern 0 hält. Dieser lenkt jeden MQTT-Fluss anhand des Toeplitz-Hashes seines Vierertupels auf einen der Arbeitskerne und sendet, was die Arbeitskerne zurückgeben. Eine Sitzung lebt auf ihrem Heimatkern, der durch den Hash der Client-ID bestimmt wird, sodass Übernahme, Last Wills und Persistenz nie Kerngrenzen überschreiten. Eine Verbindung, die auf einem anderen Kern landet, behält dort ihr TCP und TLS und reicht den gewöhnlichen MQTT-Verkehr mit kreditbasierter Flusskontrolle an den Heimatkern weiter. Jede weitergeleitete Nachricht wird zudem auf die übrigen Broker-Teile verteilt, von denen jeder eine Replik der Retained Messages hält.
Drei Regeln halten dieses Modell zusammen. Run-to-Completion: Ein empfangenes Paket wird vollständig auf einem einzigen Kern verarbeitet, von TCP über das Entschlüsseln von TLS an Ort und Stelle, das Parsen von MQTT und das Routing bis zur verschlüsselten Antwort, ohne Aufgabenwarteschlangen und ohne Kopieren in Anwendungspuffer. Adaptives Polling: Unter Last werden die Empfangswarteschlangen aktiv abgefragt, im Leerlauf kehrt der Kern zu Interrupts und WFI zurück; die Interrupt-Handler quittieren den Interrupt nur und markieren die Arbeit. Watchdog-Timer: Ein 10-ms-Tick pro Kern fängt Handler ab, die ihr Budget überschreiten.
Das Leben eines PUBLISH
Das Gerät sendet einen TLS-Record. Die Netzwerkkarte übergibt ihn per Deskriptor ohne Kopieren an die Schleife des Kerns. Die Schleife prüft TCP, entschlüsselt TLS an Ort und Stelle und parst MQTT aus dem geliehenen Puffer, dann prüft sie im Speicher die ACL, die Ratenbegrenzung und den Pfad durch den Topic-Baum. Interessiert sich ein anderer Kern für das Topic, erhält er eine Routing-Nachricht mit einer Referenz auf die Nutzlast und der Topic-Kennung; die Datenbank erhält einen Telemetrieeintrag oder die Aktualisierung einer Retained Message, gebündelt in einem Paket. Das PUBACK wird direkt in den Ausgangspuffer verschlüsselt und mit dem TCP-ACK verschmolzen, sodass nur ein einziges Segment zum Gerät geht. Einen anderen Kern kostet die Nachricht eine Verschlüsselung pro Abonnent im Moment des Sendens.
Was eine Verbindung kostet
Das Ziel sind höchstens 512 Byte pro inaktiver TLS-Verbindung, mit der Tendenz zu 256. Solange eine Verbindung ruht, wird für sie nichts in einem Puffer gehalten: Empfangene Daten werden direkt aus dem Netzwerkframe verarbeitet, und die Puffer für Senden, Records und MQTT existieren nur, solange etwas unvollendet oder unbestätigt ist. Der Kernel misst das auch selbst: exakte Heap-Zähler in angeforderten Bytes und in den tatsächlich belegten Blöcken, alle zehn Sekunden geschrieben als Heap über der Grundlinie pro offener Verbindung.
| Teil | Größe | Wie |
|---|---|---|
| TCP-Kontrollblock | 96 B | gepackte Flags, 16-Bit-RTT/RTO, Slot im Timer-Rad, kein Puffer im Leerlauf |
| Slot in der TCP-Tabelle | 4–8 B | nur der Verbindungsindex, Tabelle höchstens halb voll |
| Verbindung und Zustand des TLS-1.3-Records | 152 B | Secret und Sequenz pro Richtung (40 B); Schlüssel und IV werden pro Record abgeleitet |
| MQTT-Verbindung | 48 B | Identität und Last Will liegen nur so lange auf dem Heap, wie sie existieren |
| MQTT-Sitzung, heiße Form | 64 B | ein Abonnement pro Zeile, internierte Knotenkennungen |
Abonnement devices/<id>/cmd/# |
0 Baumknoten | relativ zur Sitzungskennung gespeichert, allen Geräten gemeinsam |
Gemessen unter HVF mit vier virtuellen Kernen, davon drei Arbeitskerne, wobei jedes Gerät eine mTLS-Verbindung und ein Abonnement mit QoS 1 hält, einschließlich der Rundung durch den Allokator:
| Verbindungen | Bytes pro Verbindung |
|---|---|
| 960 | 531 B |
| 9 922 | 494 B |
| 10 197 | 474 B |
Der Pool der Netzwerkframes ist mit etwa 2 MiB fest, das sind ungefähr 200 B pro Verbindung bei zehntausend Verbindungen und etwa 2 B bei einer Million. Mehr konnten wir nicht messen: Das Benutzer-Netzwerk von QEMU unter macOS hat bei 10 197 aufgehört, Verbindungen anzunehmen, ungefähr an der Grenze von 10 240 Deskriptoren des QEMU-Prozesses. Die Zahl sinkt, je mehr die festen Anteile sich auf mehr Verbindungen verteilen, sie wächst also nicht mit deren Anzahl.
Das Sicherheitsmodell
Sicherheit ist die erste Priorität und gewinnt jeden Konflikt mit den anderen Zielen. Das Modell hat mehrere Schichten:
- Ein einziger Port. TCP 8883, nur TLS 1.3, obligatorische Client-Zertifikate, signiert von der im Image eingebetteten CA. Die MQTT-Client-ID muss der Geräte-ID aus dem Zertifikat entsprechen.
- Kein Zugang. Keine Shell, keine Konsoleneingabe, kein SSH, keine Administrations-API, kein dynamischer Code, kein über das Netzwerk erreichbares SQL und kein Dateisystem.
- Speichersicherheit. Rust, mit
forbid(unsafe_code)in den Protokollpaketen; jederunsafe-Block hat einen Kommentar// SAFETY:, den ein Lint prüft, und bei externer Eingabe gibt es keine Panik: Clippy verbietet in den Paketen auf dem Datenpfadunwrap,expect, Indexierung undpanic!. - Abschwächung von Exploits. W^X wird durch die Kodierung der Deskriptoren und das Bit
SCTLR_EL1.WXNdurchgesetzt und bei jedem Start auditiert. Die Seitentabellen sind nach dem Start schreibgeschützt, ohne beschreibbaren Alias. Die untere Hälfte des Adressraums ist ausgeschlossen. Jeder Stack hat Guard-Pages, und Ausnahmen laufen auf einem eigenen Stack. PAC und BTI werden aktiviert, wenn der Prozessor sie unterstützt, und das prüfen die Smoke-Tests. - Missbrauchsresistenz. SYN-Cookies; die Admission Control lehnt neue Verbindungen vor jeglicher Kryptografie ab, wenn der Heap knapp ist, die Datenbank zurückliegt, zu viele Handshakes pro Kern oder pro Quelladresse laufen oder die Handshake-Rate zu hoch ist. Ratenbegrenzung pro Gerät, begrenzte Warteschlangen, ein an den Rückstau des Brokers gekoppeltes TCP-Fenster, begrenztes Logging, Watchdog-Timer und Health-Check.
- Unveränderlichkeit. Konfiguration, CA und Serverschlüssel sind im Image eingebettet. Jede Änderung bedeutet ein neues Image.
Das Bedrohungsmodell ist in docs/SECURITY.md festgehalten.
Speicherung ohne Dateisystem
Die Daten liegen auf einer separaten Festplatte, virtio-blk oder virtio-scsi, auf der es kein Dateisystem gibt. Das Layout ist minimal und eigen:
LBA 0 [ Superblock: Magic · Layout-Version · UUID · Regionstabelle · CRC-32C ]
LBA 8, 16 [ Tabelle der Dateigrößen, Slots A/B (der neueste gültige gewinnt) ]
LBA 64 [ SQLite-Hauptdatenbank ½ ][ SQLite-WAL ⅛ ][ Journal 1 MiB ][ Telemetrie-Journal: 8 Partitionen … ]
letzte 4 KiB [ Bereich für den Selbsttest des Blockgeräts beim Start ]
SQLite (gevendortes 3.54.1, mit Prüfung des Digests beim Build) wird mit clang ohne Standardbibliothek in den Kernel übersetzt und nutzt einen festen memsys5-Heap. Sein VFS bildet drei feste Dateien auf Regionen der Festplatte ab. Es läuft im WAL-Modus mit locking_mode=EXCLUSIVE, braucht also keinen Index im gemeinsamen Speicher, und mit synchronous=FULL; findet quick_check beim Start einen Fehler, hält sich das System sicher an. In der Datenbank liegen das Geräteregister, die Sperrungen, die Retained Messages und die persistenten Sitzungen mit Warteschlangen für QoS-1-Nachrichten für Geräte, die nicht verbunden sind.
Der Kern für die Datenbank, derjenige mit der höchsten Nummer, führt SQLite aus und sonst nichts. Die Datenebene schickt ihm Zustandsänderungen über einen sperrfreien SPSC-Ring; er sammelt sie alle 5 ms oder alle 512 Operationen in einer Transaktion und gibt die Bestätigungen über einen zweiten Ring zurück. Eine leere Festplatte wird beim ersten Start formatiert; alles andere wird niemals überformatiert.
Die Telemetrie geht einen anderen Weg. Jedes akzeptierte PUBLISH auf Topics aus storage.telemetry_topics landet in der Partition des Telemetrie-Journals seines Kerns: ein Ring aus Segmenten mit CRC pro Eintrag, gesalzen mit der Segmentsequenz, sodass alte Durchläufe des Rings nie wieder auftauchen. Die Kerne übergeben die Einträge über einen eigenen Ring ohne Kopieren an die Datenbank, die sie alle 5 ms oder alle 256 KiB ausschreibt. Beim Start behält die Wiederherstellung jeden ausgeschriebenen Eintrag, schneidet den unvollständigen Rest ab und setzt in einem neuen Segment fort. Die Aufbewahrung erfolgt nach Größe, durch den Ring, und nach Alter, durch retention_days. Wir haben das mit einer Million Einträgen und mit kill -9 mitten im Schreiben überprüft.
Die Zahlen
Das Lastwerkzeug in tools/loadgen leistet drei Dinge: einen Sturm von TLS-1.3-Handshakes mit mTLS und CONNECT, langlebige Verbindungen mit PINGREQ, die jede Sekunde Telemetrie veröffentlichen, und Wiederverbindungsstürme. make bench auf einem Apple M4 Pro unter HVF liefert:
| Arbeitskerne | Handshakes/s im Gast | Skalierung | durch das QEMU-Benutzernetzwerk (Verbindungen/s) |
|---|---|---|---|
| 1 | 862 | 1,00× | 789 |
| 2 | 1 724 | 2,00× | 636 |
| 4 | 3 438 | 3,99× | 536 |
Die Zahl im Gast, bei der Client und Server innerhalb derselben virtuellen Maschine liegen und jeder Arbeitskern für sich arbeitet, zeigt, dass die Datenebene linear skaliert. Die Netzwerkzahl wird durch das einthreadige Benutzernetzwerk von QEMU begrenzt, nicht durch den Gast; auch das ist als ADR-032 festgehalten.
Wiederverbindungsstürme testen wir, indem sich alle Geräte gleichzeitig verbinden, abgelehnte Versuche mit Verzögerung wiederholen und dann alle ohne DISCONNECT abstürzen. Der Parameter heap_limit_mb= bringt die Admission Control im Entwicklungsmodus dazu, den Heap für kleiner zu halten, als er ist, sodass sich ein Sturm, der größer ist als der Heap, mit wenigen Sockets ausprobieren lässt. In einer solchen Runde mit 3 000 Geräten verbanden sich 1 368 in 36 Sekunden, und 5 619 Versuche wurden ordnungsgemäß abgelehnt, bei einem Limit von 8 MiB; der Speicher kehrte nach dem Sturm zurück.
Wie wir arbeiten
Das Projekt wird von einer schriftlichen Spezifikation, PROMPT.md, mit einem Meilensteinplan geleitet, und jeder Meilenstein endet mit demselben Ritual: make check und make smoke grün, ein Commit mit der Kennzeichnung M<n>, ein Eintrag in docs/PROGRESS.md und ein ADR für jede Entscheidung, die nicht offensichtlich ist. Die Regeln sind kurz, und wir verhandeln nicht darüber: keine Panik bei externer Eingabe, unsafe nur in Hardware- und FFI-Paketen und immer mit Kommentar, keine Allokationen auf dem heißen Pfad, ein Kommentar zu jeder Reihenfolge atomarer Operationen, jedes Limit kommt aus der Konfiguration, und absichtliche vorläufige Teile scheitern laut.
Die CI leistet mehr, als wir von Hand schaffen würden: fmt, Clippy für den Host und für aarch64-unknown-none, typos, actionlint und shellcheck; Tests unter Linux und macOS auf arm64; den Build des Kernels für die Entwicklungs- und die Produktionskonfiguration mit der ELF-Größe in der Zusammenfassung; den Start unter QEMU mit allen Szenarien des Smoke-Tests und das Sichern der seriellen Logs; cargo-deny für Advisories, Lizenzen und Quellen; eine Minute Fuzzing pro Ziel bei allen elf Zielen und nachts zehn Minuten pro Ziel mit einem zwischengespeicherten Korpus. Dazu CodeQL, OpenSSF Scorecard, Dependabot und eine Veröffentlichung mit SLSA-Provenienz. Jede fremde Action ist auf den vollständigen Commit-SHA festgepinnt.
Reproduzierbarkeit nehmen wir wörtlich: make repro baut HEAD zweimal in frischen Arbeitsbäumen und vergleicht ELF, ISO und rohes Abbild Byte für Byte. Die Releases sind mit minisign signiert, und die Artefakte lassen sich mit gh attestation verify prüfen. Das Produktions-Image enthält den privaten Serverschlüssel, daher ist das ISO selbst ein Geheimnis; die Images bei den Releases auf GitHub sind Entwicklungs-Images mit temporären Schlüsseln und dienen nur zum Ausprobieren.
Was wir gelernt haben
Einige Lektionen, die den Plan verändert haben:
- Ein eigenes TCP hat sich doch gelohnt. Der Prototyp lief auf smoltcp und hat uns bis M13 gute Dienste geleistet. Aber ein Server, der Hunderttausende Verbindungen hält, braucht kein Client-TCP, er braucht SYN-Cookies, ein Timer-Rad und einen Zustand von 96 Byte. In M14 haben wir smoltcp durch einen eigenen Stack nur für die Serverseite ersetzt, mit Tests unter simuliertem Paketverlust und Fuzzing der Segmente.
- Messen Sie den Speicher von innen. Schätzungen pro Verbindung waren nutzlos, bis der Kernel selbst begann, den Heap in Allokatorblöcken zu zählen und nicht in angeforderten Bytes. Erst dann sahen wir, wohin die 512 Byte tatsächlich gehen, und kamen auf 494 herunter.
- Das Testwerkzeug ist Teil des Produkts. Die größte Einschränkung war nicht der Kernel, sondern das Benutzer-Netzwerk von QEMU und die Zahl der ephemeren Ports auf dem Entwicklungsrechner. Für hunderttausend und mehr Verbindungen braucht man
vmnet-sharedunter macOS oder Linux mit KVM, einem tap-Interface und mehreren Client-Adressen. - SQLite ohne Betriebssystem ist möglich, aber nur unter strengen Bedingungen: fester Heap, exklusives Locking ohne gemeinsamen Speicher, ein VFS, das von Dateien nichts weiß, und eine Admission Control, die die Datenbank nicht zurückfallen lässt.
- Wiederverbindungsstürme sind der Normalzustand, keine Ausnahme. Fällt das Mobilfunknetz aus und kehrt zurück, klopfen Tausende Geräte gleichzeitig an. Deshalb lehnt die Admission Control eine Verbindung vor jeglicher Kryptografie ab, und jede abgelehnte Verbindung kostet fast nichts.
Was als Nächstes kommt
Die Arbeit nach M17 ist in drei Richtungen geplant, und die Reihenfolge dazwischen ist in docs/PLAN.md festgehalten. Nichts davon ist bisher gebaut.
- Ein ISO für mehrere Plattformen. Dieselbe Datei
urmqtt-<env>-<version>.isound ein einziges rohes GPT-Abbild, die auf aarch64-UEFI, x86_64-UEFI und x86_64-BIOS starten, in virtuellen Maschinen und auf echter Hardware, beginnend mit einem Intel-PC. Weiterhin ein Appliance-Gerät mit allem darin, reproduzierbar und signiert. - Rohe Festplatte, kein Dateisystem. Eine Formatspezifikation auf Byte-Ebene, das Host-Werkzeug
urmqtt-disk, das die Parser mit dem Kernel teilt, Ausrichtung der Regionen auf 1 MiB, Wachstum der Festplatte mit den Daten, Discard für Thin-Provisioning-Festplatten, A/B-Snapshots der Datenbank, Bit-Prüfung und, optional, Verschlüsselung mit AES-256-XTS mit Hardware-AES. - Nachweislich sicher. Sperrung und Zulassungsliste von Geräten zur Laufzeit über einen signierten Steuerkanal, ohne neues Image; differenzielles und Konformitätstesten des eigenen TLS und TCP; KASLR, Stack-Schutz, kCFI, MTE und ein gehärteter Allokator; SQLite auf der Ebene EL0 isoliert; Secure Boot; SBOM und cargo-vet; und am Ende ein Paket für die externe Revision.
Plattformen, die nur Nicht-virtio-Geräte anbieten, wie AWS ENA oder Azure MANA, werden vorerst nicht unterstützt. Raspberry Pi 4 und 5 als KVM-Host, Hetzner CAX und Oracle Ampere A1 sind dokumentiert und vom Code unterstützt, aber noch nicht auf einem echten Dienst ausprobiert.
Probieren Sie es aus
Das Repository befindet sich unter github.com/zeljan-alduk/urMQTT. Mit Rust samt festgepinnter Nightly-Toolchain und QEMU genügen make certs, make run und ein mosquitto-Client für das erste PUBLISH über mTLS auf Ihrem eigenen Rechner. Die Warnung vom Anfang gilt auch am Ende: Dies ist Pre-Alpha, und alles, was funktioniert, steht in docs/PROGRESS.md. Fehlermeldungen, Fragen zum Design und Sicherheitsmeldungen gemäß der Anleitung in SECURITY.md sind willkommen. Über jeden weiteren Meilenstein berichten wir hier.
Kommentare
Kommentarregeln