Ansible en Bitwarden Personal

Ansible is een systeem voor het configureren van applicaties en systemen. De Ansible-configuratie wordt vaak gepubliceerd in een Git-archief of repository bij services als Github of Gitlab.

De combinatie van Ansible, Git en vertrouwelijke informatie komt met een structureel probleem: vertrouwelijke gegevens opnemen in Git is een serieus veiligheidsrisico. Bitwarden is een tool voor het veilig opslaan van vertrouwelijke gegevens als wachtwoorden en toegangssleutels. Door Bitwarden aan de mix van tools toe te voegen, kan het risico op lekken van vertrouwelijke informatie worden beperkt.

Ansible en Git vormen een zeer krachtige combinatie. Met het versiebeheer van Git is het mogelijk om de wijzigingen in de Ansible draaiboeken, rollen en configuraties bij te houden en eventueel terug te draaien.
Door te werken in aparte branches (vertakkingen) werken teamleden aan eenzelfde project zonder elkaar in de weg te zitten. Tegelijkertijd kunnen ze elkaars code beoordelen, voordat deze wordt ingevoegd in de hoofdtak.

Ansible stelt je infrastructuur voor als code, en Git is een perfect hulpmiddel om code te beheren. Door Git met Ansible te gebruiken, kun je infrastructuurcode behandelen als een softwareproject, waarbij je best practices voor softwareontwikkeling toepast, zoals versiebeheer, samenwerking en continue integratie.

Wanneer vetrouwelijke gegevens in Git terecht komen, is deze toegankelijk voor iedereen met toegang tot Git. In het geval van een publieke repository, kan in principe iedereen erbij. Ook wanneer deze gegevens verwijderd worden uit een bestand, blijven de gegevens zelf door het versiebeheer in Git staan en blijven ze ook vindbaar. En wanneer het al lukt om de gegevens uit het centrale Git-archief te verwijderen, kunnen deze gegevens nog door het decentrale karakter van Git nog steeds in kopieën blijven rondzwerven.

Ansible kan zo ingesteld worden dat het tijdens configuratie de vertrouwelijke gegevens uit Bitwarden haalt. Zo komen er geen vertrouwelijke gegevens terecht in een Git-archief. Bijkomend voordeel is dat bij wijziging van een wachtwoord of toegangssleutel er in Ansible niets moet worden aangepast.

Ansible heeft ook een eigen oplossing beschikbaar voor bescherming van vertrouwelijke gegevens, namelijk Ansible Vault. Een techniek om een bestand met vertrouwelijke gegevens te versleutelen, voordat toegevoegd wordt aan een Git-repository. Deze techniek heeft nadelen, zoals dat het is mogelijk om de versleuteling te vergeten.

Er zijn meerdere oplossingen beschikbaar voor een veilig gebruik van betrouwbare gegevens in Ansible. Hieronder wordt de variant uitgewerkt met Bitwarden Vault. Vergelijkbare en betere oplossingen zijn ook beschikbaar voor alternatieve wachtwoordmanagers van Bitwarden zelf en andere leveranciers.

Bitwarden CLI

Bitwarden op de opdrachtregel. De schakel tussen Ansible en een persoonlijke Bitwarden Vault.

De integratie van Bitwarden in een Ansible Playbook bestaat uit twee onderdelen. De eerste bouwsteen is de Ansible module Bitwarden Lookup.

Bitwarden CLI

Een Ansible Lookup module leest informatie uit een externe bron, in dit geval een Bitwarden Vault. Het Ansiblescript maakt daarbij gebruik van de tweede bouwsteen: de Bitwarden CLI (command-line interface). De Bitwarden CLI is te downloaden als een uitvoerbaar bestand en eenvoudig te installeren. Het is daarbij wel belangrijk dat Ansible straks deze applicatie kan vinden.

Naast dat Ansible de Bitwarden CLI ofwel het uitvoerbare bestand moet kunnen vinden, is er nog wat voorbereidend werk nodig. U moet via de Bitwarden CLI aangemeld zijn en de Vault moet ontgrendeld zijn. Tenslotte moet er een specifieke omgevingsvariabele met de sessiesleutel beschikbaar zijn. Bitwarden geeft deze na het ontgrendelen van de Vault.

eldert@tumbleweed:~> bw login 
? Email address: name@domain
? Master password: [hidden]
You are logged in!

To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:
$ export BW_SESSION="5PBYGU+5yt3RHcCjoeJKx/wByU34vokGRZjXpSH7Ylo8w=="
> $env:BW_SESSION="5PBYGU+5yt3RHcCjoeJKx/wByU34vokGRZjXpSH7Ylo8w=="

You can also pass the session key to any command with the `--session` option. ex:
$ bw list items --session 5PBYGU+5yt3RHcCjoeJKx/wByU34vokGRZjXpSH7Ylo8w==

Voorbeeld van login met Bitwarden CLI

Voor het instellen van de verwijzing in Ansible naar de specifieke item in Bitwarden, hebben we de naam of id van het item nodig.

💡
Gebruik het unieke id. Die kan niet per ongeluk gewijzig worden, in tegenstelling tot de naam. Ook kan bij namen die gelijk zijn of sterk op elkaar lijken de gegevens van een ander item uitgelezen worden.

Het zoeken naar items gaat met onderstaand commando.

# bw list items --search [zoekterm]

bw list items --search umami       
[{"passwordHistory":null,"revisionDate":"2023-08-02T12:03:22.006Z","creationDate":"2023-08-02T12:02:49.010Z","deletedDate":null,"object":"item","id":"7ac9cae8-5067-4faf-b6ab-acfd00e2c328","organizationId":null,"folderId":null,"type":1,"reprompt":0,"name":"umami","notes":null,"favorite":false,"login":{"uris":[{"match":null,"uri":"https://somedomain"}],"username":"replaced","password":"something else","totp":null,"passwordRevisionDate":null},"collectionIds":[]}]

Zoals te zien levert dit een slecht leesbaar resultaat op: een niet uitgelijnde lijst van items in JSON-formaat. Het is dan ook aan te raden een tool als jq te gebruiken om de leesbaarheid te vergroten.

jq
# bw list items --search [zoekterm] | jq

[
  {
    "collectionIds": [],
    "creationDate": "2023-08-02T12:02:49.010Z",
    "deletedDate": null,
    "favorite": false,
    "folderId": null,
    "id": "7ac9cae8-5067-4faf-b6ab-acfd00e2c328",
    [...]
  }

De id, in dit geval 7ac9cae8-5067-4faf-b6ab-acfd00e2c328, hebben we straks nodig om in Ansible een verwijzing aan te maken naar dit item.

Password Manager CLI | Bitwarden Help Center
The Bitwarden command-line interface (CLI) is a powerful, fully-featured tool for accessing and managing your Vault.

Informatie over de Bitwarden CLI

Ansible Bitwarden Lookup

Deze module is onderdeel van de collectie 'community.general', die op onderstaande wijze geïnstalleerd kan worden.

ansible-galaxy collection install community.general

Nu zijn we gereed om ons Ansiblescript een wachtwoord uit Bitwarden te laten inlezen en weer te geven. Hiervoor wordt het lookup-commando gebruikt. In het eerst argument geven we aan dat we gebruik willen maken van de module community.general.bitwarden. Het tweede argument, de zoekterm, is in dit geval de id-waarde van het item in de Bitwarden Vault. Het derde argument search='id' beschrijft de zoekterm en geeft in dit geval aan dat het een id betreft. Het laatste argument field='password' geeft aan uit welke veld we informatie willen lezen, in dit geval het wachtwoordveld. Laten we dit laatste veld weg, dan wordt hele item ingelezen.

- name: "Get 'password' from Bitwarden record with id"
  ansible.builtin.debug:
    msg: >-
      {{ lookup('community.general.bitwarden', 'bafba515-af11-47e6-abe3-af1200cd18b2', search='id', field='password') }}

Het opzoeken van een item in Bitwarden Vault en uitprinten van het resultaat met Ansible debug. 

⚠️
De respons van deze module is altijd in de vorm van een lijst. Ook wanneer we zoeken met een uniek id.
De module maakt gebruik van de manier van zoeken, zoals hierboven besproken: bw list items --search.
Dit heeft ook als gevolg dat bij het zoeken op naam, het niet uitgesloten kan worden dat uit de lijst van resultaten een onjuist item ingelezen wordt.

Onderstaand voorbeeld laat zien hoe een wachtwoord uit Bitwarden ingelezen wordt als variabele in Ansible. Wat hier afwijkend is, is de toevoeging [0], aan het einde van de lookup-opdracht.

db_password: "{{ lookup('community.general.bitwarden', 'bafba515-af11-47e6-abe3-af1200cd18b2', search='id', field='password')[0] }}"

Voorbeeld van een main.yml-bestand met variabelen en Bitwarden lookup

Met de [0] selecteren we het eerste item van lijst van zoekresultaten. Doen we dat niet dan wordt in aan de variabele een lijst toegekend. In dit geval zou dat resulteren in een lijst van één item. De waarde van db_password zou dat bijvoorbeeld zijn: ['geheim-wachtwoord']. Voegen we de [0] toe, dan selecteren we het eerste item van de lijst en wordt de variabele gelijk aan het wachtwoord zelf.

community.general.bitwarden lookup – Retrieve secrets from Bitwarden — Ansible Documentation

Documentatie van de Ansiblemodule om gegevens uit Bitwarden uit te lezen.

Automatisering

In bovenstaande beschrijving zijn een aantal handmatige stappen nodig om het Ansiblescript werkend te krijgen. Het inloggen en unlocken van de Bitwardenkluis en het exporteren van een variabele met een sessiesleutel van Bitwarden.

Naast het inloggen met een gebruikersnaam en wachtwoord, kan met Bitwarden CLI ook ingelogd worden met een persoonlijke api-sleutel. Deze sleutel bestaat uit een client id en een client secret, die als omgevingsvariabelen meegegeven kunnen worden met de login-opdracht, zoals onderstaand.

Het werken met omgevingsvariabelen voor gevoelige informatie op de opdrachtregel is een veiligheidsrisico, omdat deze in de opdrachtgeschiedenis kunnen worden teruggevonden.
De omgevingsvariabelen die hier worden gebruikt geven toegang tot een complete Bitwardenkluis. Het is raadzaam om hier zeer behoedzaam mee om te gaan.
BW_CLIENTID=[client-id] BW_CLIENTSECRET=[client-secret] bw login --apikey

In tegenstelling tot de traditionele login is na een login met een apikey een expliciete unlock noodzakelijk. Opnieuw is authenticatie noodzakelijk, nu met het Bitwarden wachtwoord. Ook dit kan via een omgevingsvariabele.

BW_PASSWORD=[password] bw unlock --passwordenv BW_PASSWORD

"bw unlock" met een omgevingsvariabele voor het persoonlijke Bitwarden wachtwoord

Met bw unlock krijgen we de sessiecode die gebruikt moet worden, voor elke interactie met de Bitwarden Vault. Deze sessiecode kan als variabele beschikbaar worden gemaakt, of aan elke opdracht meegegeven worden met de optie --session [sessiecode].

Voor de Ansible lookup is het vereist dat de sessiecode als omgevingsvariabele beschikbaar is. Om dit te automatiseren is wat extra script nodig. Hieronder is een voorbeeld uitgewerkt hoe dit te realiseren in Bash.

export BW_SESSION=$(BW_PASSWORD=[persoonlijk_bw_wachtwoord] bw unlock --passwordenv BW_PASSWORD --raw)

Automatische export van BW_SESSION na uitvoer van "bw unlock" via Bash command substitution

De bw unlock opdracht krijg een extra optie mee, namelijk --raw. De opdracht geeft nu alleen de sessiecode terug, zonder verdere instructie. Via de techniek van command substitution wordt de sessiecode toegewezen aan de variabele BW_SESSION. Tenslotte wordt met export de variabele beschikbaar gemaakt voor onderliggende processen.

Evaluatie: prima voor persoonlijk, niet voor teams

Het in Ansible beschikbaar maken van gevoelige gegevens via Bitwarden is een mooie manier om deze gegevens veilig in de kluis van Bitwarden te beheren en zo buiten de code te houden en deze toch op moment van uitvoer voor Ansible beschikbaar te hebben.

Zodra we echter deze manier werken willen automatiseren of zouden willen delen in een team, schiet deze manier van werken tekort.

We zijn gebonden tot het gebruik van met persoonlijke toegangsgegevens, namelijk het wachtwoord voor Bitwarden en de persoonlijke API-sleutel. Dit is verre van ideaal. Een API-sleutel met beperkte rechten, namelijk het uitlezen van bepaalde informatie uit een bepaald item in de kluis, zou een veel mooiere, veiligere en beter te automatiseren oplossing zijn.

Door te werken met de omgevingsvariabelen en het script met daarin het persoonlijke wachtwoord ontstaat er reëel risico op het lekken van toegangsgegevens tot de Bitwarden kluis. Op deze manier creëren we een groter beveiligingsprobleem dan welke we proberen op te lossen.

Kortom, Ansible op deze manier toegang geven tot Bitwarden is prima voor persoonlijk gebruik. Zeker wanneer afgezien wordt van de mogelijkheden tot automatisering. Voor automatisering en teamgebruik is het verstandig te zoeken naar een andere oplossing.

Bitwarden Secrets Manager

Voor automatisering van teams heeft Bitwarden daarom ook een alternatief beschikbaar: de Bitwarden Secrets Manager, die gebruikt kan worden in combinatie met een Ansible lookup-module: community.general.bitwarden_secrets_manager.

Why does my development team need a secrets manager? | Bitwarden Blog
A secrets manager is a security solution for storing secrets used by developers, DevOps, and IT teams. In this article, we’ll explore the reasons why a secrets manager is essential for security-minded businesses.
community.general.bitwarden_secrets_manager lookup – Retrieve secrets from Bitwarden Secrets Manager — Ansible Documentation
The Bitwarden Secrets Manager | Bitwarden
End-to-end encrypted secrets management for development, DevOps, and IT teams.

Ansible is Simple IT Automation
Ansible is the simplest way to automate apps and IT infrastructure. Application Deployment + Configuration Management + Continuous Delivery.
The password manager trusted by millions | Bitwarden
Bitwarden makes it easy for businesses and individuals to securely generate, store, and share passwords from any location, browser, or device. Create your free Bitwarden account today.