Secrets im Git-Repo, ohne Klartext
API-Schlüssel gehören nicht unverschlüsselt ins Repository. Wie SOPS und age das hier inzwischen automatisch erledigen — und an welchen Stellen es trotzdem hakt.
- Security
- Git
- Werkzeuge
Fast jedes Projekt sammelt irgendwann Zugangsdaten: einen FTP-Zugang, einen API-Key, ein Datenbankpasswort. Die ehrliche Bilanz vieler Repositories ist, dass diese Daten entweder im Klartext darin liegen oder dass ihre Verwaltung auf dem Prinzip Hoffnung beruht — eine Zeile in der .gitignore und die stillschweigende Übereinkunft, nie aus Versehen git add . zu tippen. Beides ist kein Zustand. Dieser Artikel beschreibt, wie Secrets hier seit Kurzem versioniert werden, ohne dass jemals Klartext in ein Git-Objekt gelangt.
Der Ansatz: SOPS und age
Die Werkzeuge sind SOPS zur Verschlüsselung und age als einfacher, moderner Schlüsselmechanismus. Der private Schlüssel liegt einmalig lokal auf dem Rechner, der öffentliche Schlüssel wandert als Konfigurationsdatei in jedes Repository, das Secrets enthält. Verschlüsselt wird pro Datei, und die verschlüsselte Fassung darf bedenkenlos committet werden — ohne den privaten Schlüssel ist sie nur Rauschen.
Der eigentliche Hebel ist aber nicht das Werkzeug, sondern die Automatik dahinter. Ein Git-Filter verschlüsselt Dateien mit einschlägigen Namen beim git add transparent und entschlüsselt sie beim Checkout wieder. Auf der Platte bleibt der Klartext, im Repository landet ausschließlich Ciphertext. Wer den Filter einmal global eingerichtet hat, muss danach nicht mehr daran denken — und genau das ist der Punkt: Sicherheitsmechanismen, die Disziplin erfordern, werden irgendwann umgangen.
Als zweite Ebene läuft ein Pre-Commit-Hook, der den Commit blockiert, wenn eine Datei nach einem Secret aussieht, aber keinen Verschlüsselungsmarker trägt. Der Hook ist bewusst redundant: Er fängt genau die Fälle, in denen der Filter nicht greift — ein frisch geklontes Repository etwa, in dem der Filter noch nicht aktiv war.
Woran es trotzdem hakt
Drei Fallstricke sind der Mühe wert, sie aufzuschreiben. Erstens sucht macOS den Schlüssel an einem anderen Ort, als die Dokumentation nahelegt; die Umgebungsvariable, die das korrigiert, gehört in die Datei, die für jede Shell läuft — auch für die nicht-interaktiven Subshells, die Git bei Filtern startet. Wer sie nur in der interaktiven Konfiguration setzt, wundert sich anschließend über fehlgeschlagene Filter in GUI-Werkzeugen.
Zweitens erzeugt SOPS bei jeder Verschlüsselung einen neuen Zufalls-Nonce. Das ist ein Sicherheitsmerkmal, hat aber den Nebeneffekt, dass git status solche Dateien dauerhaft als verändert anzeigt, obwohl sich inhaltlich nichts getan hat. Unschön, aber unvermeidbar — und ein guter Grund, diese Dateien nicht aus Gewohnheit blind mitzukommitten.
Drittens sind Git-Hooks nicht Teil der Historie. Ein Hook, der im einen Klon blockiert, existiert im nächsten Klon nicht, bis er dorthin kopiert wird. Wer das vergisst, hat ein Sicherheitsnetz, das nur auf dem eigenen Rechner gespannt ist.
Was das ändert
Seit die Automatik steht, liegen die Zugangsdaten aller Projekte versioniert neben dem Code, zu dem sie gehören — verschlüsselt, nachvollziehbar, mit Historie. Ein neuer Rechner braucht nur den einen privaten Schlüssel, und das Repository bringt den Rest mit. Die vorige Praxis — Secrets in einer lokalen Datei, die in keinem Backup und keinem Repository auftaucht — war bequem, bis der erste Rechnerwechsel zeigte, was sie kostet.
Der größere Gewinn ist trotzdem ein anderer: Die Hemmschwelle, Zugangsdaten ordentlich zu behandeln, ist gesunken, weil der korrekte Weg jetzt der bequeme ist. Sicherheit, die gegen den Workflow arbeitet, verliert. Sicherheit, die unsichtbar im Workflow steckt, hält.