Už som napísal príspevok o zálohe Bitwardenu, práve na túto tému. Spočiatku som uvažoval len o jeho aktualizácii, ako som to urobil s cloudflare zálohou a neskôr s faktury-online zálohou. Po niekoľkých zmenách v tom príspevku som zistil, že som urobil toľko zmien v celej myšlienke aj v samotnom skripte, že stojí za to napísať nový príspevok.

GitHub Action workflow #

Okrem nastavenia secrets je toto jediný skript, ktorý potrebujem. Funguje bez problémov pre osobné aj firemné účty. Uložte ako .github/workflows/main.yml a ako vždy aktivujte oprávnenie na zápis pre GitHub workflows, aby bolo možné pravidelne vytvárať commity.

name: Bitwarden.com backup

on:
  workflow_dispatch:
  schedule:
    - cron: "30 0 * * 0"

jobs:
  backup:
    runs-on: ubuntu-22.04
    env:
      BW_CLIENTID: ${{ secrets.BW_CLIENTID }}
      BW_CLIENTSECRET: ${{ secrets.BW_CLIENTSECRET }}
      BW_PASSWORD: ${{ secrets.BW_PASSWORD }}
      BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4

      - run: |
          npm install
          ./node_modules/@bitwarden/cli/build/bw.js login --apikey
          ./node_modules/@bitwarden/cli/build/bw.js export --format json --session $(./node_modules/@bitwarden/cli/build/bw.js unlock --passwordenv BW_PASSWORD --raw) --raw | openssl aes-256-cbc -a -salt -pbkdf2 -k $BW_PASSWORD -out personal.json.enc
          ./node_modules/@bitwarden/cli/build/bw.js export --format json --session $(./node_modules/@bitwarden/cli/build/bw.js unlock --passwordenv BW_PASSWORD --raw) --raw  --organizationid "$BW_ORGANIZATION_ID" | openssl aes-256-cbc -a -salt -pbkdf2 -k "$BW_PASSWORD" -out organization.json.enc
          ./node_modules/@bitwarden/cli/build/bw.js lock
          ./node_modules/@bitwarden/cli/build/bw.js logout

      - if: ${{ !env.ACT }}
        uses: stefanzweifel/git-auto-commit-action@v5

Spustenie lokálne #

Je možné spustiť toto lokálne cez act, no návod je už v predchádzajúcich príspevkoch, takže tu uvediem len to, čo je bezpodmienečne potrebné:

npm i @bitwarden/cli
npx bw login

Dešifrovanie #

Záloha je uložená v zašifrovanej forme aj v repozitári. Heslo je uložené v GitHub secrets a ak dôjde k napadnutiu vášho účtu, samozrejme ho možno extrahovať, ale hej – 2FA by ste mali mať uložené na inom mieste/zariadení tak či tak. Na dešifrovanie dát pre skutočné použitie spustite nasledovné:

openssl aes-256-cbc -d -a -pbkdf2 -in personal.json.enc -out personal.json
openssl aes-256-cbc -d -a -pbkdf2 -in organization.json.enc -out organization.json

Príjemné používanie!

Doplnenie 2026-04-09 #

Záloha organizácie bola chvíľu ticho nefunkčná a ja som si to nevšimol. Secret BW_ORGANIZATION_ID mal zastaranú hodnotu — Bitwarden niekedy zmenil ID organizácie. CLI sa vôbec nesťažovalo. Exportovalo vault, nenašlo žiadne položky zodpovedajúce danému ID a vyprodukovalo tento úplne platný, ale úplne prázdny JSON:

{
  "encrypted": false,
  "collections": [
    {
      "id": "e101ecd7-10c8-438f-a6bd-aed801304538",
      "organizationId": "c987eb3d-59b7-4ef9-aad4-aed801304514",
      "name": "Default Collection",
      "externalId": null
    }
  ],
  "items": []
}

OpenSSL to zašifroval, workflow to commitol, všetko vyzeralo v poriadku. Všimol som si to až keď som zo zvedavosti súbor dešifroval a nenašiel v ňom žiadne položky.

Presnejšia oprava by bola skontrolovať JSON pomocou jq pred šifrovaním — overiť, že items | length > 0 a že organizationId v odpovedi zodpovedá $BW_ORGANIZATION_ID. Problém je urobiť to čisto. Export momentálne ide priamo rúrou do openssl, takže kontrola obsahu by znamenala buď zapísať nezašifrovaný JSON na disk (nie ideálne), alebo spustiť export dvakrát — raz na validáciu, raz na šifrovanie (plytváme a medzi volaniami sa môže niečo zmeniť). Ani jedno sa nezdalo stáť za to.

Namiesto toho sa ukázalo, že kontrola veľkosti súboru po zálohovaní je dostatočná. Zašifrovaný prázdny JSON s nesprávnym ID organizácie vychádza na zhruba 370 bajtov. Zašifrovaný súbor s akýmikoľvek reálnymi dátami vaultu bude dobre nad 500 bajtov. Ak je niektorý súbor podozrivo malý, workflow teraz vypáli varovanie GitHub Actions a krok zlyhá:

- name: Validate backup sizes
  id: validate
  continue-on-error: true
  run: |
    MIN_SIZE=500
    for file in personal.json.enc organization.json.enc; do
      size=$(wc -c < "$file")
      if [ "$size" -lt "$MIN_SIZE" ]; then
        echo "$file is too small: $size bytes (minimum $MIN_SIZE)"
        exit 1
      fi
    done

- name: Notify on backup too small
  if: steps.validate.outcome == 'failure'
  run: |
    echo "::warning::Backup validation failed - one or more encrypted files are suspiciously small"
    exit 1

continue-on-error: true pri validate kroku znamená, že commit sa stane aj pri zlyhaní — chybná záloha sa uloží, aby sa dala preskúmať. Notifikačný krok potom spustenie workflow označí ako zlyhané, čo sa zobrazí ako chybná akcia na GitHub a pošle email. Ťažko prehliadnuť.

Správne ID organizácie možno nájsť dešifrovaním nedávnej zálohy a prečítaním poľa organizationId z ľubovoľného záznamu kolekcie, ako je vidieť vyššie.