Der Connector an der Tür — und ein sicheres Auto-Update
Foto: Rawpixel (CC0 1.0), via Openverse
Die meiste Software, an der ich arbeite, lebt in der Cloud. Container Apps, ein Key Vault, ein paar Worker, die im Hintergrund Nachrichten von einem Service Bus abholen — alles bequem erreichbar, alles updatebar mit einem azd deploy. Diese Woche war anders. Diese Woche ging es um ein Stück Software, das nicht in Azure läuft, sondern in einem Technikraum neben einer Tür steht, an einem Netzteil hängt und mit einer Hardware spricht, die ich nicht per SSH erreichen kann. Sobald man Code an einen Ort schickt, an dem physisch niemand aus dem Team sitzt, verändert sich die ganze Risikorechnung.
Der Anlass ist einfach beschrieben: Für die Zutrittssteuerung in den Studios braucht die Plattform einen Vermittler, der die vorhandene Türhardware ansteuert. Das ist der Connector. Und ein Connector, der monatelang unbeaufsichtigt vor Ort läuft, muss sich selbst aktualisieren können — sonst altert er, sammelt Sicherheitslücken und wird zur Wartungslast. Genau dieses Auto-Update ist aber auch der gefährlichste Teil des ganzen Aufbaus. Deshalb steht es diese Woche im Mittelpunkt, und deshalb ist es beim Schreiben dieser Zeilen noch nicht scharf geschaltet.
Warum überhaupt ein On-Device-Connector
Die Türhardware in den Studios kommt von Gantner. Sie spricht ihre eigenen Protokolle, sie will lokal im Netzwerk erreichbar sein, und sie hat keine Vorstellung davon, dass irgendwo in der Cloud eine Blazor-App auf einen Mitgliedsausweis wartet. Zwischen dieser physischen Welt und der Plattform muss etwas übersetzen. Dieses Etwas ist ein kleiner Dienst, der vor Ort läuft — der On-Device-Connector — und der die Gantner-Türhardware mit der Plattform verbindet.
Man könnte fragen, warum das nicht direkt von der Cloud aus geht. Die Antwort ist Latenz und Erreichbarkeit: Eine Tür soll sich auch dann öffnen, wenn die Internetleitung des Studios gerade zickt, und sie soll auf ein vorgehaltenes Leser-Signal in Millisekunden reagieren, nicht in dem, was ein Roundtrip über Azure kostet. Der Connector hält die Verbindung zur Hardware lokal, meldet Ereignisse nach oben und nimmt Befehle von oben entgegen. Er ist bewusst schmal gehalten — je weniger er selbst entscheidet, desto weniger kann er falsch machen.
Das Modell ist damit klar: ein dünner, langlebiger Prozess an der Kante, der eine proprietäre Hardware kapselt und den Rest der Logik der Plattform überlässt. Was fehlt, ist die Antwort auf die unbequeme Frage, wie dieser Prozess gepflegt wird, wenn niemand vor Ort ist.
Auto-Update ist die eigentliche Angriffsfläche
Ein Update-Mechanismus ist per Definition eine Hintertür, die man selbst einbaut: ein Kanal, über den fremder Code auf ein Gerät gelangt und dort ausgeführt wird. Das ist bei einer Cloud-Deployment-Pipeline auch so, aber dort sitzt die Ausführung in einer kontrollierten Umgebung mit klaren Rechten. Beim Connector läuft der Code auf einem Rechner in einem Studio, und ein kompromittiertes Update wäre nicht bloß ein Ausfall, sondern potenziell die Kontrolle über eine Tür.
Deshalb habe ich das Auto-Update diese Woche nicht einfach aktiviert, sondern zuerst gehärtet — bevor es das erste Mal scharf läuft. Drei Vektoren standen dabei im Fokus: Woher kommt das Paket, was darf das Paket, und wer bestimmt, welches Paket überhaupt gezogen wird. Jede dieser Fragen hat eine eigene Maßnahme bekommen, und keine davon ist besonders spektakulär. Sicherheit an der Kante entsteht selten aus einem cleveren Trick, sondern aus mehreren langweiligen Riegeln, die zusammen eine Tür ergeben, an der ein Angreifer nichts zu holen findet.
Foto: Rawpixel (CC0 1.0), via Openverse
Nur signierte Pakete: Authenticode-Verifikation der MSI
Die erste Frage — woher kommt das Paket — beantwortet die Signaturprüfung. Das Update wird als MSI ausgeliefert, und bevor der Connector eine heruntergeladene MSI überhaupt anfasst, verifiziert er ihre Authenticode-Signatur. Läuft die Prüfung nicht sauber durch, wird das Paket verworfen. Nur signierte Pakete laufen; alles andere fliegt raus, noch bevor der Installer startet.
Der Wert dieser Prüfung liegt darin, dass sie unabhängig vom Transportweg greift. Selbst wenn jemand den Download-Kanal manipulieren oder eine untergeschobene Datei am richtigen Ort platzieren würde, fehlt ihm die passende Signatur. Der Connector vertraut nicht der Quelle, weil sie die richtige URL hatte, sondern dem Paket, weil es kryptografisch von uns stammt. Das ist die Grundlage, auf der alles Weitere aufbaut — ohne sie wären die anderen Maßnahmen nur Kosmetik.
Root-RCE zumachen: der Connector schreibt nur in sein eigenes data/
Die zweite Frage — was darf das Paket, beziehungsweise was darf der laufende Connector — führt zu einem Risiko, das leicht übersehen wird. Ein Prozess, der Dateien schreiben und Updates einspielen darf, ist ein attraktives Ziel: Wer ihn dazu bringt, an eine beliebige Stelle im Dateisystem zu schreiben, kann daraus schnell eine Remote-Code-Execution mit hohen Rechten basteln. Genau dieses Root-RCE-Risiko habe ich geschlossen, indem der Connector ausschließlich in ein eigenes, beschreibbares data/-Unterverzeichnis schreibt.
Alles, was der Connector zur Laufzeit ablegt — Zwischenstände, heruntergeladene Pakete, was auch immer —, bleibt in diesem einen Verzeichnis eingezäunt. Die Programmdateien selbst und alles außerhalb dieses Unterordners sind für ihn nicht beschreibbar. Der Effekt ist, dass ein manipulierter Schreibpfad ins Leere läuft: Er landet im Sandkasten, nicht in einem Systemverzeichnis. Damit wird aus einer potenziellen Rechteausweitung im schlimmsten Fall ein vollgeschriebenes data/, das man einfach leeren kann.
Die Update-Quelle festnageln: gepinnte Manifest-URL
Bleibt die dritte Frage: Wer bestimmt eigentlich, welches Paket der Connector zieht? Ein Auto-Update folgt einem Manifest — einer Liste, die sagt, welche Version aktuell ist und wo sie liegt. Wer dieses Manifest umbiegen kann, kann den Connector auf eine beliebige, eigene Update-Quelle zeigen lassen und damit die Signaturprüfung zwar nicht aushebeln, aber immerhin das Ökosystem angreifen, in dem sie stattfindet.
Deshalb ist die Manifest-URL auf der API nur CI-seitig gesetzt und damit gepinnt. Sie kommt nicht aus einer zur Laufzeit veränderbaren Konfiguration, die jemand über eine Admin-Oberfläche oder einen kompromittierten Datensatz verstellen könnte, sondern wird ausschließlich in der Build- und Deployment-Pipeline gesetzt. Niemand kann die Update-Quelle im laufenden Betrieb umbiegen. Zusammen mit der Signaturprüfung heißt das: Der Connector zieht nur von einer Quelle, die wir festgelegt haben, und akzeptiert von dort nur, was wir signiert haben.
Eine Pause-Bremse, bis die Hardware es bestätigt
So gründlich die drei Riegel auch sind — ich habe kein Interesse daran, ein Auto-Update in Produktion loszulassen, bevor es einmal an echter Hardware bewiesen hat, dass es sich vorhersehbar verhält. Deshalb gibt es eine Pause- und Pin-Bremse, die das Auto-Update aushält, bis ein On-Device-Hardware-Gate bestanden ist. Bis dahin bleibt der Mechanismus zwar eingebaut und geprüft, aber angezogen. Er zieht nichts, er installiert nichts, er wartet.
Diese Bremse ist Absicht, keine Verlegenheit. Der Unterschied zwischen „im Testaufbau funktioniert es" und „an einer echten Gantner-Tür in einem echten Studio funktioniert es" ist genau die Sorte Lücke, in der sich unangenehme Überraschungen sammeln. Für die Freigabe gibt es ein Runbook, das den Ablauf beschreibt: welche Prüfungen an der Hardware laufen müssen, wer sie abzeichnet und wie die Bremse anschließend gelöst wird. Solange das Gate nicht bestanden ist, bleibt die Bremse dran — dokumentiert und bewusst.
Mir ist wichtig, dass das hier ein ehrlicher Zwischenstand ist: Die Sicherheitsarbeit ist getan, der Auslöser ist es noch nicht. Das ist der richtige Zustand für etwas, das an einer Tür hängt. Lieber ein Connector, der noch nicht selbst aktualisiert, als einer, der es zu früh tut.
Was kommt als Nächstes?
Nächste Woche: Qualität als System — ein Test-Coverage-Programm, zentrale Paketverwaltung und ein echter Produktionsfehler, den wir gefunden haben.
Kommentare
Noch keine Kommentare. Teilen Sie als Erste(r) Ihre Gedanken!
Kommentar hinterlassen