Ansible Vault: geheimen veilig versleutelen in Git
Beheer wachtwoorden en API tokens veilig in uw repository met Ansible Vault. Leer hoe u bestanden versleutelt, staging van productie scheidt en wachtwoorden efficiënt wijzigt.
Wat Ansible Vault beschermt en wat niet
Ansible Vault versleutelt geheimen binnen uw playbook-repository, waardoor git ciphertext opslaat in plaats van wachtwoorden in platte tekst. Het ansible-vault-commando versleutelt een volledig bestand of een enkele waarde in een bestand, met behulp van een symmetrische sleutel die is afgeleid van een wachtwoord naar keuze. Ansible ontsleutelt deze inhoud in het geheugen wanneer de play wordt uitgevoerd, waardoor de variabele zich gedraagt als elke andere variabele.
Dit model heeft één duidelijke grens. Vault beschermt een geheim in rust in de repository en niets meer. Zodra een taak wordt uitgevoerd, is de waarde platte tekst in het geheugen, in de gegenereerde template, in de module-argumenten en in de uitvoer van de run, tenzij u dit blokkeert. Iedereen die het playbook kan uitvoeren, beschikt over het vault-wachtwoord; vault biedt dus geheimhouding tegenover mensen buiten het team, geen toegangscontrole per persoon binnen het team.
Als u nog geen playbook heeft geschreven, begin dan met een eerste Ansible playbook voor een VPS en kom terug wanneer dat playbook een wachtwoord vereist.
Een volledig bestand versleutelen of een enkele string?
ansible-vault encrypt vervangt een bestand door ciphertext. Het bestand wordt één blok base64-tekst onder een kopregel die begint met $ANSIBLE_VAULT. Gebruik dit wanneer het bestand uitsluitend geheimen bevat.
ansible-vault encrypt_string versleutelt één waarde en print een YAML-fragment dat u in een gewoon vars-bestand plakt. De variabelenaam blijft leesbaar en alleen de waarde is ciphertext. Gebruik dit wanneer geheimen naast instellingen in platte tekst staan.
Het verschil dat ertoe doet in het dagelijks werk is de diff. Een vault-bestand wordt elke keer dat u het opslaat opnieuw versleuteld met een nieuwe random salt, waardoor elke byte van de ciphertext verandert. git diff toont dan één onleesbaar blok dat is vervangen door een ander onleesbaar blok, wat betekent dat een reviewer niet kan zien of u één wachtwoord heeft geroteerd of het bestand heeft herschreven. Bij encrypt_string is elk geheim een eigen blok binnen een bestand in platte tekst, waardoor een diff precies laat zien welke variabele is gewijzigd en de rest van het bestand ongemoeid laat.
De inline-vorm heeft een nadeel, en dat komt naar voren bij rotatie: ansible-vault rekey raakt inline-blokken niet aan. Kies de bestandsvorm wanneer de lijst met geheimen lang is en zelden verandert. Kies de inline-vorm wanneer het bestand geheimen mengt met normale variabelen en u wilt dat code-reviews betekenisvol zijn.
De group_vars-structuur die aangeeft wat beveiligd is
Ansible laadt group_vars/<group>.yml, en het laadt ook elk bestand in een group_vars/<group>/-directory. De directory-vorm heeft de voorkeur, omdat hiermee een enkele groep een tekstbestand en een versleuteld bestand naast elkaar kan bevatten.
inventory/
hosts.ini
group_vars/
all/
vars.yml
vault.yml
web/
vars.yml
vault.yml
host_vars/
db01/
vars.yml
vault.yml
playbooks/
site.ymlElke vault.yml is versleuteld. Elke vars.yml is platte tekst. Een lezer kan zien welke waarden beveiligd zijn zonder iets te openen, omdat de bestandsnaam dit aangeeft.
De tweede helft van het patroon is indirectie. Voorzie in het versleutelde bestand elke variabele van het voorvoegsel vault_.
vault_db_password: "a real password"
vault_grafana_admin_token: "a real token"Verwijs vervolgens vanuit het ernaast gelegen tekstbestand naar deze namen.
db_password: "{{ vault_db_password }}"
grafana_admin_token: "{{ vault_grafana_admin_token }}"Rollen en templates gebruiken db_password en merken niet waar de waarde vandaan komt, wat de scheiding tussen een playbook en een rol zuiver houdt. Het tekstbestand vars.yml fungeert tevens als een doorzoekbare index: grep -r vault_ group_vars/ bevat elk geheim dat de repository verwacht, zonder dat er iets ontsleuteld hoeft te worden. De prijs hiervoor is één extra naam per geheim, en een typefout in een vault_-naam komt tijdens runtime naar voren als een niet-gedefinieerde variabele in plaats van als een syntaxfout.
Versleutel één variabele met encrypt_string
ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.txt \
--stdin-name 'vault_db_password'Typ het geheim en druk vervolgens op Ctrl-D. --stdin-name leest de waarde van de standaardinvoer, waardoor deze niet in het geschiedenisbestand van uw shell terechtkomt. De andere vorm plaatst de waarde op de opdrachtregel, waar de shell deze wel registreert:
ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.txt \
'a real password' --name 'vault_db_password'In beide gevallen print het commando een YAML-blok. Plak dit exact zoals geprint in het vars-bestand, aangezien de inspringing onder de !vault-tag onderdeel is van de waarde.
vault_db_password: !vault |
$ANSIBLE_VAULT;1.2;AES256;prod
6638643965323633646262656665306333616466396630323136393465356136396436383331
3131303163306665326539353837343663313762616561306534373963383531613664393332De !vault-tag vertelt de YAML-loader dat de scalaire waarde ciphertext is in plaats van tekst. De header bevat de versie van het formaat, de cipher en het vault ID-label waarmee het is versleuteld. Een waarde die zonder vault ID is versleuteld, bevat een 1.1-header zonder label; dit werkt nog steeds, maar geeft simpelweg minder informatie over de herkomst van het wachtwoord.
Waar wordt het vault-wachtwoord opgeslagen?
Buiten de repository. Dat is de enige regel zonder uitzonderingen.
--ask-vault-pass vraagt één keer per uitvoering om invoer en slaat niets op. Dit is geschikt voor een laptop, maar niet voor een cron-job of een CI-runner.
Een wachtwoordbestand is een plat tekstbestand waarvan de eerste regel het wachtwoord bevat. Maak het leeg aan met strikte rechten en vul het daarna in een editor, zodat het wachtwoord nooit in uw shell-geschiedenis terechtkomt:
mkdir -p ~/.ansible
install -m 600 /dev/null ~/.ansible/vault-prod.txt
$EDITOR ~/.ansible/vault-prod.txtVerwijs naar dit bestand bij elk commando met --vault-password-file:
ansible-playbook -i inventory/hosts.ini playbooks/site.yml \
--vault-password-file ~/.ansible/vault-prod.txtHet is makkelijk om deze vlag bij elk commando te vergeten, dus stel deze eenmalig in in ansible.cfg in de root van de repository.
[defaults]
inventory = inventory/hosts.ini
vault_password_file = ~/.ansible/vault-prod.txtDezelfde instelling leest vanuit de omgevingsvariabele ANSIBLE_VAULT_PASSWORD_FILE, wat de standaardmanier is waarop een CI-job het wachtwoord aanlevert. De job schrijft het wachtwoord vanuit zijn eigen credential store naar een bestand in een tijdelijke map, exporteert de variabele en verwijdert het bestand wanneer de uitvoering eindigt. Voeg het bestandsnaam-patroon ook toe aan .gitignore, omdat het pad in ansible.cfg wordt gecommit en vroeg of laat iemand het echte bestand in de checkout zal aanmaken.
Als het wachtwoordbestand uitvoerbaar is, voert Ansible het uit en leest het wachtwoord van de standaarduitvoer in plaats van het bestand als tekst te lezen. Op die manier haalt u het vault-wachtwoord uit een systeem-keyring of een cloud secret manager zonder het ooit naar de schijf te schrijven. Een script dat via --vault-id wordt gebruikt, heeft extra vereisten: de naam moet eindigen op -client of op -client plus een extensie, het moet uitvoerbaar zijn, het moet een --vault-id-optie accepteren en het moet het wachtwoord naar de standaarduitvoer printen.
Twee vault-ID's: staging en production
Een vault-ID is een label dat aan een vault-wachtwoord wordt gekoppeld, geschreven als label@source. De bron is prompt, het pad naar een wachtwoordbestand of het pad naar een client-script. Dankzij labels kan één repository geheimen onder meer dan één wachtwoord opslaan, zodat het staging-wachtwoord het production-bestand niet kan ontsleutelen.
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.ymlGeef elk ID door dat een run nodig heeft:
ansible-playbook playbooks/site.yml \
--vault-id staging@~/.ansible/vault-staging.txt \
--vault-id prod@~/.ansible/vault-prod.txtOf vermeld ze eenmalig in ansible.cfg:
[defaults]
vault_identity_list = staging@~/.ansible/vault-staging.txt, prod@~/.ansible/vault-prod.txtEén gedrag is vaak verrassend. Standaard is het label een hint, geen slot. Ansible probeert elk geheim dat het op dat moment bezit op het bestand totdat een ervan het ontsleutelt; een bestand met het label staging opent dus alsnog als het production-wachtwoord toevallig de juiste sleutel is. Stel vault_id_match = True in onder [defaults], of gebruik de omgevingsvariabele ANSIBLE_VAULT_ID_MATCH, zodat Ansible alleen het geheim gebruikt waarvan het label overeenkomt met de bestandsheader. Die controle vereist de 1.2-header en is daarom alleen van toepassing op inhoud die oorspronkelijk met een vault-ID is versleuteld.
Wanneer er meer dan één ID is geladen, weet ansible-vault encrypt niet langer met welk wachtwoord versleuteld moet worden. Geef dit op met --encrypt-vault-id prod, of stel vault_encrypt_identity in binnen ansible.cfg zodat de repository een standaardwaarde heeft.
Het voordeel is de scope van de uitrol. Een CI-job die staging uitrolt, krijgt alleen het staging-wachtwoord, waardoor een gecompromitteerde runner geen production-inloggegevens kan lezen. Zodra u playbooks uitvoert op een vloot Linux-servers vanaf één controle-machine, is die scheiding het verschil tussen een klein incident en een zeer groot incident.
De vault opnieuw versleutelen wanneer iemand vertrekt
Het opnieuw versleutelen (rekeying) wijzigt het vault-wachtwoord en versleutelt de inhoud opnieuw met het nieuwe wachtwoord. Dit maakt eerdere acties niet ongedaan. Iedereen die het oude wachtwoord kende, kan nog steeds elke kopie van de repository ontsleutelen die zij in bezit hebben, inclusief alle oude commits in die kopie. Beschouw het vault-wachtwoord daarom als gecompromitteerd zodra een houder vertrekt en voer de rotatie in deze volgorde uit.
- Wijzig de werkelijke inloggegevens op de servers en in de externe diensten. Deze stap trekt de toegang daadwerkelijk in.
- Plaats de nieuwe waarden in de vault-bestanden met
ansible-vault edit. - Versleutel elk versleuteld bestand opnieuw met een nieuw vault-wachtwoord.
- Deel het nieuwe vault-wachtwoord met de personen die het nog steeds nodig hebben, via een kanaal dat buiten de repository valt.
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 accepteert meerdere bestanden in één commando en --new-vault-id prod@prompt vraagt één keer om het nieuwe wachtwoord in plaats van dit vanaf de schijf te lezen. Behoud hetzelfde label tenzij u een reden heeft om dit te wijzigen, aangezien het label in de header van elk bestand wordt geschreven dat het commando herschrijft.
Dit is waar de inline-vorm nadelig is. ansible-vault rekey werkt op volledig versleutelde bestanden, dus een !vault-blok dat zich in een plaintext vars-bestand bevindt, blijft ongewijzigd. Zoek deze eerst op en genereer ze vervolgens elk opnieuw met encrypt_string onder het nieuwe wachtwoord:
grep -rl '!vault' group_vars/ host_vars/Dat is de volledige afweging. Inline-blokken bieden leesbare diffs en vereisen een handmatige actie tijdens de rotatie. Volledig versleutelde bestanden worden met één commando geroteerd, maar bieden geen bruikbare informatie tijdens een review.
Waarom het geheim nog steeds in uw output verschijnt
Vault is klaar op het moment dat de waarde is ontsleuteld. Ansible rapporteert het resultaat van een taak, en een module die zijn argumenten echo't, neemt de inloggegevens mee in dat rapport. Een verbose run, een --diff op een template-taak, een mislukte taak die zijn argumenten dumpt, of een callback-plugin die output naar een bestand schrijft, bevatten elk de plaintext. Het versleutelen van het bestand deed niets tegen een van deze situaties.
no_log: true is de schakelaar. Stel deze in op elke taak die inloggegevens ontvangt.
- 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 houdt vervolgens het resultaat van die taak uit de output, zodat het logboek registreert dat de taak is uitgevoerd zonder te registreren wat deze heeft verwerkt. Stel dit in het bijzonder in op loops, omdat een loop één resultaat per item rapporteert, en een loop over een lijst met inloggegevens de hele lijst rapporteert.
Er zijn vier andere plaatsen waar een ontsleuteld geheim ontsnapt, die no_log niet afdekt:
- Een bestand dat is gegenereerd vanuit een template erft de
modeenownerdie u eraan heeft gegeven. Stelmode: "0600"en een specifieke eigenaar in op alles wat inloggegevens bevat, anders eindigt het geheim als wereldwijd leesbaar op de doelhost. - Een geheim dat wordt doorgegeven aan
ansible.builtin.commandofansible.builtin.shellverschijnt in de proceslijst op de doelhost terwijl het commando wordt uitgevoerd, waar elke lokale gebruiker het kan lezen. Geef het in plaats daarvan door via een bestand of een omgevingsvariabele. - Fact caching schrijft verzamelde feiten naar de schijf op de control machine, waardoor een geregistreerde variabele die een geheim bevat, in een cachebestand kan belanden waarvan niemand denkt dat het gevoelig is.
- Hetzelfde geheim bevindt zich meestal op een tweede plaats, zoals een omgevingsbestand dat door een container wordt gelezen. De regels daarvoor zijn apart, en het buiten Compose env-bestanden houden van inloggegevens behandelt die kant van de zaak.
no_log maakt debuggen moeilijker, wat precies de bedoeling is. Verwijder het tijdelijk op een testhost wanneer een taak zich misdraagt, en zet het terug voordat de wijziging de productieomgeving bereikt.
Versleutelde bestanden lezen en bewerken zonder plaintext achter te laten
ansible-vault view group_vars/prod/vault.yml ontsleutelt naar een pager en schrijft niets naar de schijf. ansible-vault edit ontsleutelt naar een tijdelijk bestand, opent uw $EDITOR en versleutelt het opnieuw zodra u het sluit. Geef de voorkeur aan beide boven ansible-vault decrypt, dat een plaintext-bestand in de werkmap achterlaat. Een ontsleuteld vault-bestand dat per ongeluk wordt gestaged, is de meest voorkomende manier waarop echte inloggegevens in een openbare repository terechtkomen.
Git kan een leesbare diff genereren voor volledig versleutelde bestanden door ze tijdens het proces te ontsleutelen:
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' >> .gitattributesBegrijp wat dit doet voordat u het inschakelt. git diff zal nu productiegeheimen in uw terminal afdrukken, waardoor ze in uw scrollback en in elke schermdeling terechtkomen. Het is een lokaal gemak voor één persoon op één machine; houd de git config daarom lokaal en ga ervan uit dat de checkouts van anderen zich anders gedragen, tenzij zij hetzelfde instellen.
Wanneer Vault niet langer de juiste tool is
Vault is een bestandsformaat met één wachtwoord per label, en die structuur bepaalt waar de beperkingen liggen. Stap over naar een echte secret store wanneer een van de volgende punten van toepassing is.
- U heeft toegang per persoon nodig. Iedereen die het playbook uitvoert, beschikt over hetzelfde wachtwoord, en Vault-ID's splitsen toegang op basis van omgeving, nooit op basis van persoon.
- U heeft een audit trail nodig. Vault legt niets vast over wie wat heeft ontsleuteld, of wanneer.
- U heeft rotatie op basis van een schema nodig. Vault heeft geen verloopdatum en geen versiebeheer, dus niets geeft aan dat een inloggegeven in twee jaar tijd niet is gewijzigd.
- De applicatie zelf heeft het geheim nodig tijdens runtime. Een service die bij het opstarten het databasewachtwoord leest, hoort dit niet uit uw deployment-repository te lezen.
Het patroon keert dan om. Ansible stopt met het opslaan van geheimen en begint deze tijdens runtime op te halen via een lookup-plugin, vanuit HashiCorp Vault (een ander product met een verwarrend vergelijkbare naam), de secret manager van een cloudprovider, of een keyring op de control machine. De repository bevat een pad, de store bevat de waarde, en de store houdt het toegangslogboek bij. Voor een klein team dekt een zelfgehoste wachtwoordmanager met een API, zoals een Vaultwarden server, dezelfde taak op kleinere schaal.
Eén inloggegeven blijft buiten dit alles. De SSH-key die uw control machine gebruikt om de servers te bereiken, is geen Vault-probleem, omdat Ansible deze nodig heeft voordat een play kan worden uitgevoerd. Beheer dit met een agent en een passphrase, volgens de lijnen van de basisprincipes van SSH-keybeheer.
FAQ
Moet ik het volledige vars-bestand versleutelen of alleen de geheime string?
Versleutel het volledige bestand wanneer dit uitsluitend geheimen bevat, omdat één commando alles roteert en de structuur overzichtelijk blijft. Gebruik ansible-vault encrypt_string wanneer geheimen naast gewone variabelen staan; in dat geval wijzigt alleen de versleutelde waarde in een diff en kan een reviewer zien welke variabele is aangepast. Het nadeel is de rotatie. ansible-vault rekey verwerkt volledige bestanden en laat inline !vault-blokken ongemoeid, waardoor deze handmatig opnieuw moeten worden gegenereerd onder het nieuwe wachtwoord.
Waar moet het Ansible Vault-wachtwoordbestand worden opgeslagen?
Buiten de repository, met modus 0600, op een pad zoals ~/.ansible/vault-prod.txt. Verwijs ernaar met --vault-password-file, stel vault_password_file in onder [defaults] in ansible.cfg, of stel ANSIBLE_VAULT_PASSWORD_FILE in als omgevingsvariabele. Laat in CI de job het wachtwoord vanuit de eigen credential store naar een tijdelijk bestand schrijven, exporteer de variabele en verwijder het bestand na afloop van de job. Als het bestand uitvoerbaar is, voert Ansible het uit en leest het wachtwoord van de standaarduitvoer; hierdoor kunt u het uit een keyring halen in plaats van het op schijf op te slaan.
Hoe gebruik ik verschillende vault-wachtwoorden voor staging en productie?
Geef elk wachtwoord een label met --vault-id staging@/path/to/file en --vault-id prod@/path/to/file, en versleutel de bestanden van elke omgeving onder het eigen label. Geef beide ID's op tijdens runtime, of vermeld ze in vault_identity_list onder [defaults]. Standaard probeert Ansible elk geheim dat het bevat totdat er één het bestand ontsleutelt; stel daarom vault_id_match = True in als u wilt dat het alleen het geheim probeert waarvan het label overeenkomt met de bestandsheader. Bij meerdere geladen ID's kiest u het versleutelende ID met --encrypt-vault-id.
Voorkomt Ansible Vault dat een wachtwoord in de uitvoer van een run verschijnt?
Nee. Vault beschermt het geheim alleen in rusttoestand in de repository. Zodra een taak wordt uitgevoerd, is de waarde leesbare tekst (plaintext), en een verbose run of een mislukte taak kan deze in het logboek opnemen. Voeg no_log: true toe aan elke taak die een credential verwerkt, stel een restrictieve mode en owner in op elk bestand dat u via een template genereert, en vermijd het doorgeven van geheimen als commando-argumenten, omdat deze zichtbaar zijn in de proceslijst op de doelhost terwijl het commando draait.