Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/Skidbladnir

.nornir/openbao-design.md

openbao — design

Class: design · Feature: openbao (opt-in; pulls reqwest). States the shape we ship. The why (the secret-ownership split) is in openbao-guide.md and Njord's .nornir/tillsynia-admin.md.

What it is

skidbladnir::openbao::OpenBao — a small privileged client that provisions the structure of an OpenBao (Vault-compatible) secret store over its REST API. It is the platform's half of a two-party split:

Who Owns How
Skidbladnir (openbao) vault structure — k8s auth method, per-env read policy, per-env role this module, over the shared vault_http::VaultHttp transport (twinned with pki.rs)
tillsynia-admin secret values — secret/data/<env>/app vault-write (reads the live k8s Secret)
the app reads its path at runtime OpenBao Agent injector (vault.hashicorp.com/* annotations)

This mirrors the pki module's "Vault as a seam" stance: real HTTP when the feature is on, no vault crate, nothing pulled into the offline core when off.

Shared transport (reuse-law L5). The token-authenticated POST {addr}/v1/{path} (+ X-Vault-Token header + JSON body) is NOT hand-rolled here — it lives ONCE in skidbladnir::vault_http::VaultHttp (returns (StatusCode, String)), consumed by both OpenBao::post and pki.rs's HttpVaultClient. Only the RESPONSE handling stays local: OpenBao::post swallows a 400 "already in use" as idempotent success, whereas pki surfaces the error and parses the leaf. The vault_http module is gated on the union of the two reqwest-pulling features (vault | openbao); the RED-when-broken both_callers_route_through_the_shared_vault_http_transport test proves both callers emit POST /v1/<path> with the token header via the one transport.

Surface

let ob = OpenBao::from_env()?;                    // VAULT_ADDR + VAULT_TOKEN
ob.enable_kubernetes_auth()?;                     // idempotent (400 "already in use" = ok)
ob.configure_kubernetes("https://kubernetes.default.svc")?;
ob.provision_env("test1", "test1-njord", "test1")?;   // read policy + role in one call

// Whole-platform structural populate as a PURE, ordered plan (Tier-0.5 hook):
let plan = PopulatePlan::for_platform("https://kubernetes.default.svc",
                                      &[("prod","holger-sa","holger"), ("staging","holger-sa","staging")]);
ob.apply(&plan)?;                                 // thin idempotent live executor
// lower level: write_policy(name, hcl) · write_kubernetes_role(role, sa, ns, policies, ttl)
//              env_read_policy(env) -> HCL (pure)

Deliberately OUT of scope

examples/openbao-provision.rs drives it live from env:sa:ns args.