Ansible Vault: Geheimnisse sicher in Git verschlüsseln
Erfahren Sie, wie Sie Passwort und API-Token in Git schützen, Dateien oder einzelne Werte verschlüsseln, Staging und Produktion trennen und Schlüssel sauber neu setzen.
Was Ansible Vault schützt und was nicht
Ansible Vault verschlüsselt Geheimnisse in Ihrem Playbook-Repository. In git wird dadurch Chiffretext statt eines Passworts im Klartext gespeichert. Der Befehl ansible-vault verschlüsselt entweder eine vollständige Datei oder einen einzelnen Wert innerhalb einer Datei. Dafür wird ein symmetrischer Schlüssel verwendet, der aus einem von Ihnen gewählten Passwort abgeleitet wird. Ansible entschlüsselt diesen Inhalt während der Ausführung des Playbooks im Speicher. Die Variable verhält sich dadurch wie jede andere Variable.
Dieses Modell hat eine klare Grenze. Vault schützt ein Geheimnis im Repository im Ruhezustand und nicht darüber hinaus. Sobald eine Aufgabe ausgeführt wird, liegt der Wert im Speicher, in der gerenderten Vorlage, in den Modulargumenten und in der Ausgabe des Laufs im Klartext vor, sofern Sie dies nicht verhindern. Jeder, der das Playbook ausführen kann, besitzt das Vault-Passwort. Vault schützt Sie daher vor Personen außerhalb des Teams, bietet aber keine Zugriffskontrolle für einzelne Personen innerhalb des Teams.
Wenn Sie noch kein Playbook geschrieben haben, beginnen Sie mit einem ersten Ansible-Playbook für einen VPS und kehren Sie hierher zurück, sobald dieses Playbook ein Passwort benötigt.
Eine ganze Datei oder eine einzelne Zeichenfolge verschlüsseln?
ansible-vault encrypt ersetzt eine Datei durch Chiffretext. Die Datei besteht anschließend aus einem einzelnen Base64-Textblock unter einer Header-Zeile, die mit $ANSIBLE_VAULT beginnt. Verwenden Sie diese Form, wenn die Datei ausschließlich Geheimnisse enthält.
ansible-vault encrypt_string verschlüsselt einen einzelnen Wert und gibt einen YAML-Ausschnitt aus, den Sie in eine normale Variablendatei einfügen. Der Variablenname bleibt lesbar, nur der Wert ist Chiffretext. Verwenden Sie diese Form, wenn Geheimnisse neben Klartexteinstellungen stehen.
Der relevante Unterschied im täglichen Betrieb zeigt sich im Diff. Eine Vault-Datei wird bei jedem Speichern mit einem neuen zufälligen Salt erneut verschlüsselt. Dadurch ändert sich jedes Byte des Chiffretexts. git diff zeigt dann, dass ein unlesbarer Block durch einen anderen unlesbaren Block ersetzt wurde. Ein Reviewer kann daher nicht erkennen, ob Sie ein einzelnes Passwort geändert oder die gesamte Datei neu geschrieben haben. Mit encrypt_string ist jedes Geheimnis ein eigener Block innerhalb einer Klartextdatei. Ein Diff zeigt dadurch genau, welche Variable geändert wurde, und lässt den restlichen Dateiinhalt unverändert.
Die Inline-Form hat einen Nachteil, der bei der Rotation sichtbar wird: ansible-vault rekey ändert keine Inline-Blöcke. Wählen Sie die Dateiform, wenn die Liste der Geheimnisse lang ist und sich selten ändert. Wählen Sie die Inline-Form, wenn die Datei Geheimnisse mit normalen Variablen mischt und Code-Reviews aussagekräftig sein sollen.
Das group_vars-Layout zeigt, welche Werte geschützt sind
Ansible lädt group_vars/<group>.yml. Außerdem lädt es jede Datei innerhalb eines Verzeichnisses group_vars/<group>/. Die Verzeichnisform ist die geeignete Variante, weil eine einzelne Gruppe dadurch eine Klartextdatei und eine verschlüsselte Datei nebeneinander enthalten kann.
inventory/
hosts.ini
group_vars/
all/
vars.yml
vault.yml
web/
vars.yml
vault.yml
host_vars/
db01/
vars.yml
vault.yml
playbooks/
site.ymlJede vault.yml ist verschlüsselt. Jede vars.yml liegt im Klartext vor. Anhand des Dateinamens ist erkennbar, welche Werte geschützt sind, ohne dass eine Datei geöffnet werden muss.
Die zweite Hälfte des Musters ist die Indirektion. Stellen Sie in der verschlüsselten Datei jeder Variablen vault_ voran.
vault_db_password: "a real password"
vault_grafana_admin_token: "a real token"Verweisen Sie dann in der danebenliegenden Klartextdatei auf diese Namen.
db_password: "{{ vault_db_password }}"
grafana_admin_token: "{{ vault_grafana_admin_token }}"Rollen und Templates verwenden db_password und müssen nicht wissen, woher der Wert stammt. Dadurch bleibt die Trennung zwischen Playbook und Rolle sauber. Die Klartextdatei vars.yml dient zusätzlich als durchsuchbarer Index: grep -r vault_ group_vars/ listet jedes vom Repository erwartete Secret auf, ohne etwas zu entschlüsseln. Der Nachteil ist ein zusätzlicher Name pro Secret. Ein Tippfehler in einem vault_-Namen wird zur Laufzeit als undefinierte Variable und nicht als Syntaxfehler sichtbar.
Eine Variable mit encrypt_string verschlüsseln
ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.txt \
--stdin-name 'vault_db_password'Geben Sie das Secret ein und drücken Sie anschließend Ctrl-D. --stdin-name liest den Wert von der Standardeingabe. Dadurch wird er nicht in der History-Datei Ihrer Shell gespeichert. Bei der anderen Form steht der Wert in der Befehlszeile, wo die Shell ihn protokolliert:
ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.txt \
'a real password' --name 'vault_db_password'In beiden Fällen gibt der Befehl einen YAML-Block aus. Fügen Sie ihn genau wie ausgegeben in die vars-Datei ein, da die Einrückung unter dem Tag !vault Bestandteil des Werts ist.
vault_db_password: !vault |
$ANSIBLE_VAULT;1.2;AES256;prod
6638643965323633646262656665306333616466396630323136393465356136396436383331
3131303163306665326539353837343663313762616561306534373963383531613664393332Das Tag !vault weist den YAML-Loader darauf hin, dass es sich bei dem Skalar um Chiffretext und nicht um Text handelt. Der Header enthält die Formatversion, die Chiffre und die Bezeichnung der Vault-ID, mit der der Wert verschlüsselt wurde. Ein ohne Vault-ID verschlüsselter Wert enthält einen 1.1-Header ohne Bezeichnung. Das funktioniert weiterhin, gibt jedoch weniger Aufschluss darüber, woher das Passwort stammt.
Wo befindet sich das Vault-Passwort?
Außerhalb des Repositorys. Das ist die einzige Regel ohne Ausnahmen.
--ask-vault-pass fragt einmal pro Ausführung nach dem Passwort und speichert es nirgends. Das eignet sich für einen Laptop, aber nicht für einen Cronjob oder einen CI-Runner.
Eine Passwortdatei ist eine Klartextdatei, deren erste Zeile das Passwort enthält. Erstellen Sie sie mit restriktiven Berechtigungen zunächst leer und tragen Sie das Passwort anschließend in einem Editor ein. So gelangt das Passwort nie in den Verlauf Ihrer Shell:
mkdir -p ~/.ansible
install -m 600 /dev/null ~/.ansible/vault-prod.txt
$EDITOR ~/.ansible/vault-prod.txtÜbergeben Sie sie mit --vault-password-file an einen beliebigen Befehl:
ansible-playbook -i inventory/hosts.ini playbooks/site.yml \
--vault-password-file ~/.ansible/vault-prod.txtDieses Flag bei jedem Befehl erneut anzugeben, wird leicht vergessen. Setzen Sie es daher einmal in ansible.cfg im Stammverzeichnis des Repositorys:
[defaults]
inventory = inventory/hosts.ini
vault_password_file = ~/.ansible/vault-prod.txtDieselbe Einstellung liest die Umgebungsvariable ANSIBLE_VAULT_PASSWORD_FILE. So stellt ein CI-Job das Passwort normalerweise bereit. Der Job schreibt das Passwort aus seinem eigenen Credential Store in eine Datei in einem temporären Verzeichnis, exportiert die Variable und löscht die Datei am Ende der Ausführung. Nehmen Sie das Dateinamensmuster ebenfalls in .gitignore auf, weil der Pfad in ansible.cfg versioniert wird und früher oder später jemand die echte Datei innerhalb des Checkouts anlegt.
Wenn die Passwortdatei ausführbar ist, führt Ansible sie aus und liest das Passwort aus ihrer Standardausgabe, statt die Datei als Text zu lesen. So können Sie das Vault-Passwort aus einem System-Keyring oder einem Cloud-Secret-Manager abrufen, ohne es überhaupt auf der Festplatte zu speichern. Ein Skript, das über --vault-id verwendet wird, muss zusätzliche Anforderungen erfüllen: Sein Name muss mit -client oder mit -client plus einer Erweiterung enden, es muss ausführbar sein, eine Option --vault-id akzeptieren und das Passwort auf die Standardausgabe schreiben.
Zwei Vault-IDs: Staging und Produktion
Eine Vault-ID ist eine Bezeichnung für ein Vault-Passwort, die als label@source angegeben wird. Der Wert kann prompt, der Pfad zu einer Passwortdatei oder der Pfad zu einem Client-Skript sein. Mit Bezeichnungen kann ein Repository Geheimnisse unter mehr als einem Passwort speichern. Das Staging-Passwort kann dann beispielsweise nicht die Produktionsdatei öffnen.
ansible-vault encrypt --vault-id staging@~/.ansible/vault-staging.txt \
group_vars/staging/vault.yml
ansible-vault encrypt --vault-id prod@~/.ansible/vault-prod.txt \
group_vars/prod/vault.ymlÜbergeben Sie alle IDs, die ein Lauf benötigt:
ansible-playbook playbooks/site.yml \
--vault-id staging@~/.ansible/vault-staging.txt \
--vault-id prod@~/.ansible/vault-prod.txtOder listen Sie sie einmalig in ansible.cfg auf:
[defaults]
vault_identity_list = staging@~/.ansible/vault-staging.txt, prod@~/.ansible/vault-prod.txtEin Verhalten überrascht viele Anwender. Standardmäßig ist die Bezeichnung ein Hinweis und keine Sperre. Ansible versucht jedes aktuell geladene Geheimnis mit der Datei, bis eines davon die Datei entschlüsselt. Eine mit staging bezeichnete Datei lässt sich daher trotzdem öffnen, wenn das Produktionspasswort zufällig der richtige Schlüssel ist. Setzen Sie vault_id_match = True unter [defaults] oder die Umgebungsvariable ANSIBLE_VAULT_ID_MATCH. Dann verwendet Ansible nur das Geheimnis, dessen Bezeichnung mit dem Datei-Header übereinstimmt. Diese Prüfung benötigt den 1.2-Header. Sie gilt daher nur für Inhalte, die ursprünglich mit einer Vault-ID verschlüsselt wurden.
Wenn mehr als eine ID geladen ist, weiß ansible-vault encrypt nicht mehr, mit welchem Passwort verschlüsselt werden soll. Geben Sie die ID mit --encrypt-vault-id prod an oder setzen Sie vault_encrypt_identity in ansible.cfg, damit das Repository einen Standardwert verwendet.
Der Vorteil liegt in der Begrenzung des Bereitstellungsumfangs. Ein CI-Job, der Staging bereitstellt, erhält nur das Staging-Passwort. Ein kompromittierter Runner kann dadurch keine Produktionsanmeldedaten lesen. Sobald Sie Plays über eine Flotte von Linux-Servern von einer zentralen Steuerungsmaschine aus ausführen, entscheidet diese Trennung darüber, ob ein Vorfall begrenzt bleibt oder sehr große Auswirkungen hat.
Tresor neu verschlüsseln, wenn jemand das Team verlässt
Beim erneuten Verschlüsseln werden das Tresorkennwort geändert und die Inhalte mit dem neuen Kennwort erneut verschlüsselt. Dadurch wird nichts rückgängig gemacht. Wer das alte Kennwort einmal besaß, kann weiterhin jede Kopie des Repositorys entschlüsseln, die diese Person behalten hat, einschließlich aller älteren Commits in dieser Kopie. Betrachten Sie das Tresorkennwort daher in dem Moment als kompromittiert, in dem eine berechtigte Person das Team verlässt, und führen Sie die Rotation in dieser Reihenfolge durch.
- Ändern Sie die tatsächlichen Zugangsdaten auf den Servern und in den Diensten von Drittanbietern. Nur dieser Schritt entzieht den Zugriff tatsächlich.
- Schreiben Sie die neuen Werte mit
ansible-vault editin die Tresordateien. - Verschlüsseln Sie jede verschlüsselte Datei mit einem neuen Tresorkennwort neu.
- Übergeben Sie das neue Tresorkennwort den Personen, die es weiterhin benötigen, über einen Kanal außerhalb des Repositorys.
ansible-vault rekey --vault-id prod@~/.ansible/vault-prod-old.txt \
--new-vault-id prod@prompt \
group_vars/prod/vault.yml host_vars/db01/vault.ymlrekey akzeptiert mehrere Dateien in einem Befehl, und --new-vault-id prod@prompt fragt das neue Kennwort einmal ab, statt es von der Festplatte zu lesen. Verwenden Sie dasselbe Label weiter, sofern kein Grund für eine Änderung besteht, da das Label in den Header jeder Datei geschrieben wird, die der Befehl neu schreibt.
Hier entstehen die Nachteile der Inline-Form. ansible-vault rekey arbeitet mit vollständig verschlüsselten Dateien. Ein !vault-Block in einer unverschlüsselten Vars-Datei bleibt daher unverändert. Suchen Sie diese Blöcke zuerst und erzeugen Sie anschließend jeden einzelnen mit encrypt_string unter dem neuen Kennwort neu:
grep -rl '!vault' group_vars/ host_vars/Das ist der vollständige Kompromiss. Inline-Blöcke ermöglichen lesbare Diffs, erfordern bei der Rotation jedoch einen manuellen Durchlauf. Vollständig verschlüsselte Dateien lassen sich mit einem Befehl rotieren, liefern bei der Überprüfung aber keine nützlichen Einblicke.
Warum das Geheimnis weiterhin in Ihrer Ausgabe erscheint
Vault ist in dem Moment abgeschlossen, in dem der Wert entschlüsselt wurde. Ansible meldet das Ergebnis einer Aufgabe. Ein Modul, das seine Argumente ausgibt, übernimmt die Zugangsdaten in diesen Bericht. Ein ausführlicher Lauf, ein --diff bei einer Template-Aufgabe, eine fehlgeschlagene Aufgabe, die ihre Argumente ausgibt, oder ein Callback-Plugin, das die Ausgabe in eine Datei schreibt, enthält dann jeweils den Klartext. Die Verschlüsselung der Datei verhindert keines dieser Probleme.
no_log: true ist der Schalter. Setzen Sie ihn bei jeder Aufgabe, die Zugangsdaten erhält.
- name: Write the application environment file
ansible.builtin.template:
src: app.env.j2
dest: /etc/myapp/app.env
owner: myapp
group: myapp
mode: "0600"
no_log: trueAnsible hält das Ergebnis dieser Aufgabe anschließend aus der Ausgabe zurück. Im Log wird dadurch nur festgehalten, dass die Aufgabe ausgeführt wurde, nicht aber, welche Daten sie verarbeitet hat. Setzen Sie den Schalter insbesondere bei Schleifen, weil eine Schleife ein Ergebnis pro Element meldet. Eine Schleife über eine Liste mit Zugangsdaten würde andernfalls die gesamte Liste melden.
Vier weitere Stellen können ein entschlüsseltes Geheimnis offenlegen. Keine davon wird durch no_log abgedeckt:
- Eine aus einem Template erzeugte Datei übernimmt die von Ihnen gesetzten Werte für
modeundowner. Setzen Sie für alle Dateien mit Zugangsdatenmode: "0600"und einen bestimmten Eigentümer. Andernfalls ist das Geheimnis auf dem Zielhost für alle Benutzer lesbar. - Ein an
ansible.builtin.commandoderansible.builtin.shellübergebenes Geheimnis erscheint während der Ausführung des Befehls in der Prozessliste des Zielhosts. Jeder lokale Benutzer kann es dort lesen. Übergeben Sie es stattdessen über eine Datei oder eine Umgebungsvariable. - Das Fact-Caching schreibt gesammelte Fakten auf dem Steuerrechner auf die Festplatte. Dadurch kann eine registrierte Variable mit einem Geheimnis in einer Cache-Datei landen, die niemand als schützenswert betrachtet.
- Dasselbe Geheimnis befindet sich meistens an einer zweiten Stelle, beispielsweise in einer von einem Container eingelesenen Umgebungsdatei. Dafür gelten eigene Regeln. Halten Sie Zugangsdaten aus Compose-Umgebungsdateien heraus behandelt diesen Aspekt.
no_log erschwert das Debugging. Genau dafür ist die Option vorgesehen. Entfernen Sie sie vorübergehend auf einem Testhost, wenn sich eine Aufgabe fehlerhaft verhält. Setzen Sie sie wieder, bevor die Änderung die Produktionsumgebung erreicht.
Verschlüsselte Dateien lesen und bearbeiten, ohne Klartext zurückzulassen
ansible-vault view group_vars/prod/vault.yml entschlüsselt den Inhalt in einen Pager und schreibt nichts auf die Festplatte. ansible-vault edit entschlüsselt den Inhalt in eine temporäre Datei, öffnet Ihren $EDITOR und verschlüsselt die Datei beim Schließen erneut. Verwenden Sie beide Varianten bevorzugt gegenüber ansible-vault decrypt. Dabei bleibt eine Klartextdatei im Arbeitsverzeichnis liegen. Eine versehentlich zum Staging vorgemerkte entschlüsselte Vault-Datei ist der häufigste Weg, auf dem ein echtes Zugangsdatengeheimnis in ein öffentliches Repository gelangt.
Git kann für vollständig verschlüsselte Dateien einen lesbaren Diff erzeugen, indem es sie währenddessen entschlüsselt:
git config --local diff.ansible-vault.textconv "ansible-vault view --vault-password-file ~/.ansible/vault-prod.txt"
printf '%s\n' 'group_vars/**/vault.yml diff=ansible-vault' >> .gitattributesMachen Sie sich vor der Aktivierung mit den Auswirkungen vertraut. git diff gibt nun Produktionsgeheimnisse in Ihrem Terminal aus. Dadurch landen sie im Scrollback und in jeder Bildschirmfreigabe. Dies ist eine lokale Erleichterung für eine Person auf einem Rechner. Belassen Sie daher die Einstellung git config lokal. Rechnen Sie damit, dass die Checkouts anderer Personen sich anders verhalten, sofern diese die gleiche Konfiguration nicht ebenfalls einrichten.
Wenn Vault nicht mehr das richtige Werkzeug ist
Vault ist ein Dateiformat mit einem Passwort pro Bezeichnung. Diese Struktur bestimmt, wo seine Grenzen liegen. Verwenden Sie einen echten Secret Store, sobald eine der folgenden Bedingungen erfüllt ist.
- Sie benötigen einen Zugriff pro Person. Jede Person, die das Playbook ausführt, besitzt dasselbe Passwort. Vault IDs trennen den Zugriff nach Umgebung, niemals nach Person.
- Sie benötigen eine Audit-Spur. Vault protokolliert weder, wer welche Daten entschlüsselt hat, noch wann dies geschehen ist.
- Sie benötigen eine planmäßige Rotation. Vault kennt weder Ablaufzeiten noch Versionierung. Daher weist nichts darauf hin, dass sich ein Zugangsdatenwert seit zwei Jahren nicht geändert hat.
- Die Anwendung selbst benötigt das Secret zur Laufzeit. Ein Dienst, der sein Datenbankpasswort beim Start einliest, sollte es nicht aus Ihrem Deployment-Repository beziehen.
Das Muster kehrt sich dann um. Ansible speichert keine Secrets mehr, sondern ruft sie zur Laufzeit über ein Lookup-Plugin ab. Als Quelle dienen HashiCorp Vault (ein anderes Produkt mit einem verwirrend ähnlichen Namen), der Secret Manager eines Cloud-Anbieters oder ein Keyring auf der Steuerungsmaschine. Das Repository enthält einen Pfad, der Store enthält den Wert, und der Store führt das Zugriffsprotokoll. Für ein kleines Team deckt ein selbst gehosteter Passwortmanager mit API, beispielsweise ein Vaultwarden-Server, dieselbe Aufgabe in kleinerem Umfang ab.
Eine Zugangsdatenart bleibt davon ausgenommen. Der SSH-Schlüssel, den Ihre Steuerungsmaschine für den Zugriff auf die Server verwendet, ist kein Vault-Problem, weil Ansible ihn benötigt, bevor ein Play ausgeführt werden kann. Verwalten Sie ihn mit einem Agent und einer Passphrase, etwa nach den Grundlagen der SSH-Schlüsselverwaltung.
FAQ
Sollte ich die gesamte vars-Datei oder nur die geheime Zeichenfolge verschlüsseln?
Verschlüsseln Sie die gesamte Datei, wenn sie ausschließlich Geheimnisse enthält. Dann rotiert ein Befehl alle Werte, und die Struktur bleibt einfach. Verwenden Sie ansible-vault encrypt_string, wenn sich neben normalen Variablen auch Geheimnisse in der Datei befinden. Dann ändert sich in einem Diff nur der verschlüsselte Wert, und ein Reviewer sieht, welche Variable geändert wurde. Der Nachteil betrifft die Rotation. ansible-vault rekey verschlüsselt ganze Dateien und lässt Inline-Blöcke von !vault unverändert. Diese müssen daher beim Wechsel des Passworts manuell neu erzeugt werden.
Wo sollte die Passwortdatei für Ansible Vault gespeichert werden?
Außerhalb des Repositorys mit dem Modus 0600, beispielsweise unter ~/.ansible/vault-prod.txt. Verweisen Sie mit --vault-password-file darauf. Alternativ setzen Sie vault_password_file unter [defaults] in ansible.cfg oder ANSIBLE_VAULT_PASSWORD_FILE in der Umgebung. Lassen Sie den CI-Job das Passwort aus seinem eigenen Credential Store in eine temporäre Datei schreiben, die Variable exportieren und die Datei am Ende des Jobs löschen. Wenn die Datei ausführbar ist, führt Ansible sie aus und liest das Passwort aus der Standardausgabe. Dadurch kann das Passwort aus einem Keyring bezogen werden, ohne es auf der Festplatte zu speichern.
Wie verwende ich unterschiedliche Vault-Passwörter für Staging und Produktion?
Vergeben Sie mit --vault-id staging@/path/to/file und --vault-id prod@/path/to/file jeweils eine Kennung für jedes Passwort, und verschlüsseln Sie die Dateien der einzelnen Umgebungen mit der jeweiligen Kennung. Übergeben Sie beide IDs zur Laufzeit oder führen Sie sie unter [defaults] in vault_identity_list auf. Standardmäßig probiert Ansible jedes gespeicherte Geheimnis aus, bis es die Datei entschlüsseln kann. Setzen Sie vault_id_match = True, wenn nur das Geheimnis mit der Kennung aus dem Dateikopf verwendet werden soll. Wenn mehrere IDs geladen sind, wählen Sie mit --encrypt-vault-id die Kennung zum Verschlüsseln aus.
Verhindert Ansible Vault, dass ein Passwort in der Laufzeitausgabe erscheint?
Nein. Vault schützt das Geheimnis nur im Ruhezustand im Repository. Sobald eine Task ausgeführt wird, liegt der Wert als Klartext vor. Eine ausführliche Ausführung oder eine fehlgeschlagene Task kann ihn daher in das Log schreiben. Fügen Sie jeder Task, die ein Credential verarbeitet, no_log: true hinzu. Setzen Sie für jede templatisierte Datei restriktive Werte für mode und owner. Übergeben Sie Geheimnisse außerdem nicht als Argumente an Befehle. Diese sind während der Ausführung in der Prozessliste des Zielhosts sichtbar.