OpenBao v Docker Compose — init, unseal a AppRole
Co tato dovednost pokrývá
Provoz self-hosted OpenBao (open-source fork HashiCorp Vaultu) jako služby Docker Compose na jednom hostu: první inicializace, unseal po každém restartu a jeho typické chyby, zapnutí secrets engines, přihlašování aplikace přes AppRole místo statického tokenu, ukládání a čtení KV secrets, výměna unseal klíčů (rekey), nový root token, zálohy a obnova a nakonec znovuzaložení storu, když se klíče ztratí. Je to provozní vrstva — návrh politik a integraci konkrétní knihovny řeší dokumentace OpenBao .
Kdy ji použít
- Při zprovoznění OpenBao v Compose stacku vedle aplikace, která z něj čte secrets
- Po restartu hostu nebo kontejneru, když aplikace hlásí, že secret nejde přečíst (store je sealed)
- Když unseal hlásí chybu, nebo když chcete vyměnit unseal klíče či root token
- Při přidávání nového secretu nebo nové aplikace s vlastní AppRole a politikou
- Před upgradem nebo migrací — záloha storage
Služba v Compose
openbao:
image: openbao/openbao:2.4.1
command: server -config=/openbao/config/openbao.hcl
environment:
BAO_ADDR: http://127.0.0.1:8200 # CLI uvnitř kontejneru jinak zkouší https
volumes:
- ./openbao/config:/openbao/config:ro
- openbao-data:/openbao/file
healthcheck:
# healthy = API odpovídá; zapečetěný store není chyba kontejneru
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8200/v1/sys/health?sealedcode=200&uninitcode=200"]
interval: 10s
restart: unless-stopped
# žádné ports: — klienti chodí po interní síti Compose
volumes:
openbao-data: # pojmenovaný volume, jinak data zmizí s kontejnerem
# openbao/config/openbao.hcl
storage "file" {
path = "/openbao/file"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = true # jen na privátní síti Compose; ven vždy přes TLS
}
api_addr = "http://openbao:8200"
ui = false
Všechny příkazy níže se pouštějí z adresáře s Compose souborem jako docker compose exec openbao bao … — port nemusí být vůbec publikovaný na host.
Tokeny a klíče do terminálu
Většina potíží při ruční obsluze není v OpenBao, ale v tom, jak se token nebo klíč dostane do příkazu. read -rs je správná cesta (hodnota nezůstane v historii ani na obrazovce), má ale tři vlastnosti, na které se snadno zapomene:
- Nic nevypíše — žádnou výzvu, žádné hvězdičky. Kurzor jen čeká, až vložíte hodnotu a stisknete Enter.
- Pouštějte ho samostatně, nikdy jako součást vloženého bloku. Když se do terminálu vloží víc řádků najednou,
readjako vstup spolkne další vložený řádek (nebo prázdný) a proměnná zůstane bez tokenu — potichu. Všechna další volání pak končí403 permission denied. - Vkládejte pravým tlačítkem nebo Ctrl+Shift+V (Windows Terminal, WSL); ve skrytém vstupu se vložení nijak neprojeví, takže není vidět, jestli proběhlo.
Proto hodnotu po načtení hned očistěte od neviditelných znaků a zkontrolujte délku (tabulka uvádí typické délky; u tokenu je skutečná kontrola bao token lookup):
read -rs BAO_TOKEN
BAO_TOKEN=$(printf %s "$BAO_TOKEN" | tr -cd 'A-Za-z0-9._-'); export BAO_TOKEN; echo "délka: ${#BAO_TOKEN}"
| Hodnota | Očekávaná délka |
|---|---|
root token, OpenBao 2.x (s.…; jiné tokeny se liší) | 26 |
| unseal klíč, base64 | 44 (bez = na konci) |
| unseal klíč, hex | 66 |
AppRole role_id / secret_id | 36 (UUID) |
Délka 0 znamená, že se nic nenačetlo. Když se skrytý vstup vzpírá, jsou dvě náhradní cesty:
read -rp "Token: " BAO_TOKEN # viditelně…
clear; export BAO_TOKEN; echo "délka: ${#BAO_TOKEN}" # …a hned smazat obrazovku
nebo přes dočasný soubor, který se úplně vyhne vkládání do terminálu:
install -m 600 /dev/null ~/.bao-token && nano ~/.bao-token
export BAO_TOKEN=$(tr -cd 'A-Za-z0-9._-' < ~/.bao-token); echo "délka: ${#BAO_TOKEN}"
rm ~/.bao-token # po skončení práce
Proměnná platí jen v okně terminálu, kde jste ji načetli — v jiném okně je prázdná.
První start: init
docker compose up -d openbao
docker compose exec openbao bao status # Initialized false, Sealed true
docker compose exec openbao bao operator init -key-shares=5 -key-threshold=3
init vypíše jednou pět unseal klíčů a root token. Hned je přesuňte do password manageru — ne do souboru v repozitáři, ne do .env, ne do chatu nebo ticketu. Klíče jsou v base64 — 44 znaků bez = na konci (dílek má 33 bajtů, takže padding nevzniká); hex podoba má 66 znaků. S -format=json dostanete obě podoby, unseal_keys_b64 a unseal_keys_hex; ukládejte a používejte jen jednu z nich.
Inicializujte hned po startu: sys/init nevyžaduje autentizaci, takže kdokoli na stejné síti by server mohl inicializovat jako první a klíče si nechat. Pokud bao status ukáže Initialized true u serveru, který jste neinicializovali, neodpečeťujte ho — smažte volume a začněte znovu.
Jediný operátor může zvolit -key-shares=1 -key-threshold=1; pak ale jeden klíč otevírá všechno.
Unseal — po initu a po každém restartu
docker compose exec openbao bao operator unseal # 3× s různými klíči
docker compose exec openbao bao status # Sealed false
Příkaz se na klíč ptá skrytě, takže klíč nezůstane v historii shellu. bao status vrací exit code 0 (odpečetěno), 2 (zapečetěno) a 1 (chyba) — hodí se do skriptů a monitoringu:
docker compose exec -T openbao bao status >/dev/null; echo $?
Rozpracovaný unseal (např. po špatném klíči) vynulujete:
docker compose exec openbao bao operator unseal -reset
Když vložení do skrytého promptu opakovaně selže, předejte klíč jako argument — přes proměnnou, ať nezůstane v historii (na okamžik je vidět v ps, na hostu s jedním uživatelem přijatelné):
read -rs K
K=$(printf %s "$K" | tr -cd 'A-Za-z0-9+/='); echo "délka: ${#K}"; docker compose exec openbao bao operator unseal "$K"; unset K
| Chyba | Co znamená |
|---|---|
'key' must be a valid hex or base64 string | Vložený text není klíč — nejčastěji root token (začíná s.), nebo celý řádek výpisu včetně Unseal Key 2: . Průběh zůstává, stačí vložit správný klíč. |
failed to compute combined key: all parts must be the same length | Jeden klíč má jinou délku — zkomolený, s mezerou navíc, nebo jiný formát (b64 vs. hex). -reset a znovu. |
cipher: message authentication failed po třetím klíči | Klíče jsou z jiného initu (store byl mezitím založen znovu). |
Klient dostává 503 / Vault is sealed | Store po restartu nikdo neodpečetil. |
403 permission denied u každého příkazu s -e BAO_TOKEN | BAO_TOKEN je prázdný nebo z jiného initu — zkontrolujte délku (viz Tokeny a klíče do terminálu
). |
Mounty: KV, transit, AppRole
Jednou po prvním unsealu, s root tokenem načteným do BAO_TOKEN podle předchozí sekce
. Nejdřív ověřte, že je to opravdu root token tohoto storu:
docker compose exec -T -e BAO_TOKEN openbao bao token lookup | grep policies # [root]
docker compose exec -e BAO_TOKEN openbao bao secrets enable -path=secret kv-v2
docker compose exec -e BAO_TOKEN openbao bao secrets enable transit # volitelné: jen pokud přes něj něco šifrujete
docker compose exec -e BAO_TOKEN openbao bao auth enable approle
docker compose exec -e BAO_TOKEN openbao bao secrets list
docker compose exec -e BAO_TOKEN openbao bao auth list
AppRole pro aplikaci
Aplikace nemá držet statický token. Přihlásí se dvojicí role_id + secret_id a dostane krátce žijící token, který smí jen to, co dovolí její politika.
# openbao/policies/myapp.hcl — jen čtení vlastních secrets
path "secret/data/myapp/*" {
capabilities = ["read"]
}
# politika a role (s root tokenem z předchozího kroku)
docker compose exec -T -e BAO_TOKEN openbao bao policy write myapp - < openbao/policies/myapp.hcl
docker compose exec -e BAO_TOKEN openbao bao write auth/approle/role/myapp \
token_policies=myapp token_ttl=5m token_max_ttl=15m
# role_id (není tajný) — vypíše se, zkopírujte do .env
docker compose exec -e BAO_TOKEN openbao bao read -field=role_id auth/approle/role/myapp/role-id
# secret_id (je tajný) — rovnou do .env, nikdy na terminál (upravte název proměnné)
touch .env && chmod 600 .env
printf 'MYAPP_SECRET_ID=%s\n' "$(docker compose exec -T -e BAO_TOKEN openbao \
bao write -f -field=secret_id auth/approle/role/myapp/secret-id)" >> .env
Obě hodnoty patří do .env aplikace (gitignored), pak kontejner aplikace znovu vytvořte (docker compose up -d app), aby je načetl. Ověření, že login funguje — vypíše jen TTL, ne token:
read -rs SECRET_ID # samostatně, pak vložit secret_id a Enter
printf %s "$SECRET_ID" | docker compose exec -T openbao \
bao write -field=token_duration auth/approle/login role_id=<role_id> secret_id=-
unset SECRET_ID
Rotace secret_id: vydat nový, vyměnit v .env, znovu vytvořit kontejner a starý zničit podle accessoru:
docker compose exec -e BAO_TOKEN openbao bao list auth/approle/role/myapp/secret-id
docker compose exec -e BAO_TOKEN openbao bao write \
auth/approle/role/myapp/secret-id-accessor/destroy secret_id_accessor=<accessor>
Co smí který token
Při ladění politik se vyplatí zeptat přímo serveru, místo čtení HCL:
docker compose exec -e BAO_TOKEN openbao bao policy read myapp
docker compose exec -e BAO_TOKEN openbao bao token capabilities secret/data/myapp/api_token # co smí váš token
docker compose exec -e BAO_TOKEN openbao bao token capabilities -accessor <accessor> secret/data/myapp/api_token # co smí cizí token, bez jeho hodnoty
Pozor na prefix KV v2: politika musí jmenovat secret/data/… (čtení hodnoty) a secret/metadata/… (výpis a verze), i když CLI s -mount=secret píše jen myapp/….
KV secrets
Hodnotu posílejte přes stdin (value=-), ať se neobjeví v historii ani ve výpisu procesů:
read -rs SECRET # samostatně, pak vložit hodnotu a Enter
printf %s "$SECRET" | docker compose exec -T -e BAO_TOKEN openbao \
bao kv put -mount=secret myapp/api_token value=-
unset SECRET
docker compose exec -e BAO_TOKEN openbao bao kv list -mount=secret myapp
docker compose exec -e BAO_TOKEN openbao bao kv metadata get -mount=secret myapp/api_token # verze, bez hodnoty
docker compose exec -T -e BAO_TOKEN openbao bao kv get -mount=secret -field=value myapp/api_token | wc -c # délka místo hodnoty
docker compose exec -e BAO_TOKEN openbao bao kv rollback -mount=secret -version=1 myapp/api_token
Secret uložte dřív, než aplikaci přepnete na čtení z OpenBao. Jakmile ho čte odtud, nemá mít druhý zdroj — viz fail closed níže.
Audit log
Úložiště secrets bez auditu neodpoví na otázku „kdo a kdy četl tenhle secret“. Od OpenBao 2.4 se audit zařízení deklaruje v konfiguraci serveru; zapnutí přes API (bao audit enable) je od 2.3.2 zakázané, pokud v konfiguraci nepovolíte unsafe_allow_api_audit_creation — file device totiž umí zapisovat kamkoli.
# openbao/config/openbao.hcl
audit "file" "file" {
description = "Audit log do souboru"
options {
file_path = "/openbao/logs/audit.log"
}
}
# do služby openbao v Compose
volumes:
- openbao-logs:/openbao/logs
# a openbao-logs: do horních volumes:
Nový pojmenovaný volume patří rootovi a proces OpenBao neběží jako root, proto mu ho před prvním startem jednou předejte — jinak nemůže otevřít audit.log a podle pravidla níže odmítá všechny požadavky:
docker compose run --rm --no-deps --user root --entrypoint chown openbao -R openbao:openbao /openbao/logs
docker compose up -d openbao # restart → zase unseal
docker compose exec -e BAO_TOKEN openbao bao audit list
docker compose exec openbao tail -n 3 /openbao/logs/audit.log
Hodnoty v záznamech jsou HMAC, ne čitelné secrets. Dvě věci k provozu: když OpenBao do žádného audit zařízení nedokáže zapsat, požadavky odmítá (plný disk = výpadek), a log roste bez omezení — dejte mu logrotate nebo ho posílejte do centrálního logování (file_path = "stdout" a sběr přes Docker logging driver).
Aplikace dostává 403: postup hledání
Fail-closed aplikace hlásí jen „secret nejde přečíst“. Příčin je víc a v řetězu na sebe navazují, takže je projděte popořadě od OpenBao k aplikaci (app je služba aplikace, MYAPP_* její proměnné):
# 1. OpenBao je inicializovaný a odpečetěný
docker compose exec openbao bao status | grep -E 'Initialized|Sealed'
# 2. Aplikace na něj dosáhne — z jejího kontejneru, ne z hostu
docker compose exec app curl -s -o /dev/null -w '%{http_code}\n' http://openbao:8200/v1/sys/health
# 200 ok · 503 sealed · 501 neinicializovaný · 000 jiná síť nebo DNS
# (prázdný výstup nebo 000 může znamenat i image bez curl — zkuste: docker compose exec app wget -qO- http://openbao:8200/v1/sys/health)
# 3. Kontejner aplikace má AppRole v prostředí (délky, ne hodnoty; obě 36)
docker compose exec app sh -c 'echo role=${#MYAPP_ROLE_ID} secret=${#MYAPP_SECRET_ID}'
# 4. Kontejner běží s aktuální konfigurací (Recreate = běží starší verze)
docker compose up -d --dry-run 2>&1 | grep -i recreate
# 5. AppRole login projde (viz AppRole výše)
# 6. Secret existuje a má čekanou délku
docker compose exec -T -e BAO_TOKEN openbao bao kv get -mount=secret -field=value myapp/api_token | wc -c
# 7. Politika role ho smí číst
docker compose exec -e BAO_TOKEN openbao bao policy read myapp
Krok 3 a 4 bývají příčinou častěji, než by se čekalo: hodnoty v .env se do běžícího kontejneru nepropíšou samy, je potřeba docker compose up -d app.
Root token: zrušit a v případě potřeby vyrobit znovu
Root token slouží jen k nastavení. Až existují role a secrets, zrušte ho:
docker compose exec -e BAO_TOKEN openbao bao token revoke -self
unset BAO_TOKEN
Nový root token se vyrobí z unseal klíčů:
docker compose exec openbao bao operator generate-root -init # vypíše OTP a nonce
docker compose exec openbao bao operator generate-root # 3× s unseal klíči → encoded token
docker compose exec openbao bao operator generate-root -decode=<encoded_token> -otp=<otp>
Rekey: nová sada unseal klíčů
Když klíče chcete vyměnit (odešel člověk, který jeden držel, klíče byly uložené nepořádně) — potřebujete prahový počet současných klíčů:
docker compose exec openbao bao operator rekey -init -key-shares=5 -key-threshold=3
docker compose exec openbao bao operator rekey # 3× se starými klíči → 5 nových
docker compose exec openbao bao operator rekey -status
docker compose exec openbao bao operator rekey -cancel # rozpracovaný rekey zahodit
Nové klíče se vypíšou jednou, stejně jako u initu. Staré po dokončení přestanou platit.
Zálohy a obnova (file storage)
File storage zálohujte se zastaveným kontejnerem, ať je kopie konzistentní. Záloha je zašifrovaná — bez unseal klíčů je k ničemu, s nimi je to celý store:
docker compose stop openbao
docker run --rm -v <projekt>_openbao-data:/data:ro -v ~/backups/openbao:/backup alpine \
tar czf /backup/openbao-$(date -u +%Y%m%dT%H%M%SZ).tgz -C /data .
docker compose start openbao # a znovu unseal
Obnova do prázdného volume:
docker compose stop openbao
docker run --rm -v <projekt>_openbao-data:/data -v ~/backups/openbao:/backup alpine \
sh -c 'find /data -mindepth 1 -delete && tar xzf /backup/<soubor>.tgz -C /data'
docker compose start openbao # unseal klíči z doby zálohy
S Raft storage místo file se zálohuje snapshotem za běhu: bao operator raft snapshot save /tmp/bao.snap.
Upgrade OpenBao
Na jednom hostu je upgrade výměna image nad stejnými daty:
# 1. záloha pořízená při zastaveném OpenBao (viz Záloha a obnova) — je i cesta zpět: až nová verze
# data zmigruje, starý image na nich už nemusí nastartovat
# 2. nová verze image v Compose, např. openbao/openbao:2.4.1 → novější 2.4.x
docker compose pull openbao
docker compose up -d openbao
docker compose exec openbao bao operator unseal # 3×, nový proces je zapečetěný
docker compose exec openbao bao status | grep -E 'Version|Sealed'
Před skokem na vyšší minor verzi si přečtěte release notes — mezi verzemi se mění chování, které rozbije provoz (příklad: od 2.3.2 už nejde zapnout audit přes API). Verze zvedejte postupně, ne přes několik minor verzí najednou. Cesta zpět: zastavte OpenBao, obnovte zálohu do volume (viz Záloha a obnova), vraťte starý image a odpečeťte klíči z doby zálohy.
Migrace z HashiCorp Vaultu
OpenBao vychází z Vaultu 1.14, takže CLI i API jsou kompatibilní a většina skriptů projde po záměně vault → bao a VAULT_* → BAO_*. Přenos dat je ale opatrná záležitost. Oficiální průvodce
je otestovaný jen pro Vault Community 1.14.1 (Raft, Shamir) a výslovně říká, že novější verze Vaultu jsou netestované a kompatibilita storage není zaručená. Hlavní body:
- Vault starší než 1.14.1 nejdřív upgradovat na 1.14.1; Vault Enterprise testovaný není.
- Před migrací záloha (Raft snapshot) a soupis mountů (
vault secrets list,vault auth list) — mounty s pluginem, který OpenBao nemá, se při startu přeskočí a je potřeba je vypnout. - Z konfigurace odstranit
disable_mlock(OpenBao mlock od 2.0 nepoužívá). - Nové tokeny mají jiný formát (
s.,b.,r.); klienti, kteří formát tokenu kontrolují, se můžou rozbít.
U malého single-host nasazení bývá jednodušší založit čistý OpenBao a secrets do něj přenést znovu (kv get → kv put), než migrovat storage.
Znovuzaložení storu (klíče jsou ztracené)
Bez prahového počtu platných unseal klíčů se k datům nedostanete — ani rekey, ani nový root token. Zbývá store smazat a začít znovu. Nevratné: zmizí všechny secrets, politiky, AppRole i transit klíče (a s nimi platnost všeho, co jimi bylo podepsáno). Předtím volume zazálohujte, kdyby se klíče našly.
docker compose stop openbao
# záloha volume (viz výše), pak:
docker compose rm -f openbao
docker volume rm <projekt>_openbao-data
docker compose up -d openbao
docker compose exec openbao bao operator init -key-shares=5 -key-threshold=3
docker compose exec openbao bao operator unseal # 3×
Po novém initu je všechno, co aplikace znala, neplatné. Postup:
- Mounty a politiky znovu (sekce výše).
- AppRole znovu — staré
role_idasecret_idv.envaplikace nefungují, i když role se jmenuje stejně. - Secrets znovu uložit — dřív, než aplikace začne číst, jinak fail closed odmítá.
- Kontejner aplikace převytvořit (
docker compose up -d app), aby načetl nové hodnoty, a projít postup hledání 403 .
Záludnosti (jeden host, Compose)
- Fail closed je správně. Aplikace, která čte secret z OpenBao, nemá při zapečetěném storu tiše sáhnout po proměnné prostředí — tím by se vrátil secret, který měl OpenBao nahradit. Endpoint, který secret potřebuje, má odmítat (403/503) a log má říct, který klíč se nepřečetl. Počítejte s tím, že po každém restartu hostu služba stojí, dokud někdo neodpečetí store, a monitorujte to (
bao statusexit code 2). - Manuální unseal vs. auto-unseal. Bez auto-unsealu (cloud KMS, transit jiného Vaultu/OpenBao) je unseal ruční krok člověka. Na jednom hostu to je cena za to, že klíč nikdy neleží na disku vedle dat.
- Healthcheck kontejneru.
/v1/sys/healthvrací 503 při sealed a 501 před initem. Bezsealedcode=200&uninitcode=200vypadá zapečetěný, ale zdravý server jako rozbitý kontejner: služby sdepends_on: condition: service_healthynenastartují adocker compose up --waitselže. BAO_ADDRv kontejneru. CLI ve výchozím stavu zkoušíhttps://127.0.0.1:8200; stls_disablenastavteBAO_ADDR=http://127.0.0.1:8200, jinakbao statushlásí chybu TLS.$v.env. Docker Compose v.envinterpoluje$NĚCOjako proměnnou — heslo nebo secret s dolarem se tiše usekne (varováníThe "…" variable is not set). Dolar zdvojte ($$) nebo hodnotu dejte do jednoduchých uvozovek.- Necitlivý výstup. Při ověřování vypisujte délku nebo TTL, ne hodnotu (
… | wc -c,-field=token_duration); tokeny a secrets nemají končit v logu terminálu ani v historii (read -rs,value=-). - Po změně
.envpřevytvořit kontejner. Běžící kontejner si drží prostředí a sítě z doby, kdy vznikl;docker compose up -d appho převytvoří,restartne. Kontejner, který „funguje“ už týdny, může být mimo síť, ve které OpenBao později přibyl. - Reverse proxy po převytvoření aplikace. Když nginx překládá jméno upstreamu jen při startu, po převytvoření aplikace hlásí
could not be resolveda vrací 502. Převytvořte i proxy, nebo jí nastavteresolver 127.0.0.11s proměnnou vproxy_pass. - mlock. OpenBao od 2.0 mlock nepoužívá —
cap_add: IPC_LOCKanidisable_mlocknepotřebuje (na rozdíl od návodů pro Vault). - Port nepublikovat. OpenBao stačí na interní síti Compose; pokud musí ven, tak jen přes TLS reverse proxy a s omezením na konkrétní klienty.
Časté otázky
Co je OpenBao?
Open-source fork HashiCorp Vaultu pod Linux Foundation (licence MPL 2.0). CLI se jmenuje bao místo vault, proměnné prostředí BAO_ADDR a BAO_TOKEN místo VAULT_*, API a většina příkazů jsou stejné.
Proč je OpenBao po restartu zapečetěný (sealed)?
Hlavní šifrovací klíč storu se nikdy neukládá na disk. Po každém startu procesu ho musí složit operátor z unseal klíčů (bao operator unseal), dokud není nastavený auto-unseal přes KMS nebo jiný Vault/OpenBao. Do té doby server žádná tajemství nevydá.
Co znamená chyba „failed to compute combined key: all parts must be the same length“?
Jeden ze zadaných unseal klíčů má jinou délku než ostatní — je zkomolený při kopírování, nebo míchá formáty (base64 z běžného výpisu initu a hex z unseal_keys_hex). Průběh se zahodí; spusťte bao operator unseal -reset a zadejte tři klíče znovu ve stejném formátu.
Dají se unseal klíče vygenerovat znovu?
Pro existující store jen přes bao operator rekey, a to vyžaduje prahový počet současných klíčů. Když je nemáte, data ve storu jsou nedostupná a zbývá jen smazat storage a udělat nový bao operator init.
Proč OpenBao na všechno odpovídá „403 permission denied“, i když mám root token?
Nejčastěji proto, že proměnná BAO_TOKEN je prázdná — read -rs nic nezobrazí, a když se vloží celý blok příkazů najednou, načte místo tokenu prázdný řádek. Zkontrolujte délku (echo ${#BAO_TOKEN}); root token z initu OpenBao 2.x má podobu s.… a 26 znaků (jiné tokeny mají délku jinou — skutečná kontrola je bao token lookup).