nornir-vault — guide
Class:
guide· How to drivenornir-vault. Sibling:vault-design.md.
Create, store, lock
use nornir_vault::{Vault, PinProvider, SecretEntry, SecretString};
let mut v = Vault::create("~/.korp/korp.vault", &PinProvider::new("4913"))?;
v.set("spark", SecretEntry::SparkToken(SecretString::new(token)))?;
v.set("mtls", SecretEntry::MtlsClientIdentity {
cert_pem: SecretString::new(cert),
key_pem: SecretString::new(key),
})?;
v.lock(); // drops the DEK + all decrypted entries (zeroized)
create returns the vault unlocked and writes the encrypted header (0600). set re-seals under the DEK and persists.
Open and unlock
let mut v = Vault::open(path)?; // locked: only the header is in memory
v.unlock(&PinProvider::new(pin))?; // decrypts DEK + every entry into RAM
match v.get("spark")? { // Err(Locked) if still locked
Some(SecretEntry::SparkToken(t)) => use_token(t.expose()),
_ => {}
}
A wrong PIN returns VaultError::Unlock { remaining } and burns one attempt (persisted). Once the budget is spent, unlock returns VaultError::LockedOut — even for the correct PIN. A successful unlock resets the counter.
OS keyring (feature os-keyring)
# #[cfg(feature = "os-keyring")]
let p = nornir_vault::OsKeyringProvider::new("korp-vault", "kek");
let mut v = Vault::create(path, &p)?; // stores a random KEK in the OS keyring
// later: Vault::open(path)?.unlock(&p)? // fetches it back — no PIN prompt
The consumer must enable a keyring backend for the platform (KDE Plasma thin client → keyring/sync-secret-service or the pure-Rust keyring/async-secret-service).
Showing state in a UI (the safe view)
let s = v.state(); // VaultState — NO secret bytes
serde_json::to_string(&s)?; // safe to embed in state_json
VaultState carries locked, locked_out, method, attempts_remaining, and each entry's name + kind. Never put a SecretEntry/SecretString in a state_json, a log, or a Debug dump — the types refuse to serialize, and their Debug is redacted, but do not defeat that with .expose().
Rules of thumb
- Read secrets only after
unlock; calllock()as soon as you are done. - Keep
SecretString::expose()borrows short-lived; do not copy into aString. - The correct-but-locked-out case is intentional: reset means a fresh vault (or an operator reset), not relaunching the process.