pub struct AgeVaultProvider { /* private fields */ }Expand description
Age-encrypted vault backend.
Secrets are stored as a JSON object ({"KEY": "value", ...}) encrypted with an x25519
keypair using the age format. The in-memory secret values
are held in zeroize::Zeroizing buffers.
§File layout
<dir>/vault-key.txt # age identity (private key), Unix mode 0600
<dir>/secrets.age # age-encrypted JSON object§Initialising a new vault
Use AgeVaultProvider::init_vault to generate a fresh keypair and create an empty vault:
use std::path::Path;
use zeph_vault::AgeVaultProvider;
AgeVaultProvider::init_vault(Path::new("/etc/zeph"))?;
// Produces:
// /etc/zeph/vault-key.txt (mode 0600)
// /etc/zeph/secrets.age (empty encrypted vault)§Atomic writes
save writes to a .age.tmp sibling file first, then renames it
atomically, so a crash during write never leaves the vault in a corrupted state.
Implementations§
Source§impl AgeVaultProvider
impl AgeVaultProvider
Sourcepub fn new(
key_path: &Path,
vault_path: &Path,
) -> Result<AgeVaultProvider, AgeVaultError>
pub fn new( key_path: &Path, vault_path: &Path, ) -> Result<AgeVaultProvider, AgeVaultError>
Decrypt an age-encrypted JSON secrets file.
This is an alias for load provided for ergonomic construction.
§Arguments
key_path— path to the age identity (private key) file. Lines starting with#and blank lines are ignored; the first non-comment line is parsed as the identity.vault_path— path to the age-encrypted JSON file.
§Errors
Returns AgeVaultError on key/vault read failure, parse error, or decryption failure.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let vault = AgeVaultProvider::new(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
)?;
println!("{} secrets loaded", vault.list_keys().len());Sourcepub fn load(
key_path: &Path,
vault_path: &Path,
) -> Result<AgeVaultProvider, AgeVaultError>
pub fn load( key_path: &Path, vault_path: &Path, ) -> Result<AgeVaultProvider, AgeVaultError>
Load vault from disk, storing paths for subsequent write operations.
Reads and decrypts the vault, then retains both paths so that
save can re-encrypt and persist changes without requiring callers to
pass paths again.
This method performs blocking I/O on the calling thread. Use load_async
when calling from an async context to avoid stalling the tokio executor.
§Errors
Returns AgeVaultError on key/vault read failure, parse error, or decryption failure.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let vault = AgeVaultProvider::load(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
)?;Sourcepub async fn load_async(
key_path: &Path,
vault_path: &Path,
) -> Result<AgeVaultProvider, AgeVaultError>
pub async fn load_async( key_path: &Path, vault_path: &Path, ) -> Result<AgeVaultProvider, AgeVaultError>
Async variant of load — offloads blocking I/O to a spawn_blocking thread.
Use this when calling from an async context to avoid stalling the tokio executor.
§Errors
Returns AgeVaultError on key/vault read failure, parse error, decryption failure, or
if the blocking task panics.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let vault = AgeVaultProvider::load_async(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
).await?;Sourcepub fn save(&self) -> Result<(), AgeVaultError>
pub fn save(&self) -> Result<(), AgeVaultError>
Serialize and re-encrypt secrets to vault file using atomic write (temp + rename).
Re-reads and re-parses the key file on each call. For CLI one-shot use this is acceptable; if used in a long-lived context consider caching the parsed identity.
This method performs blocking I/O on the calling thread. Use save_async
when calling from an async context to avoid stalling the tokio executor.
§Errors
Returns AgeVaultError on encryption or write failure.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let mut vault = AgeVaultProvider::load(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
)?;
vault.set_secret_mut("MY_TOKEN".into(), "tok_abc123".into(), false)?;
vault.save()?;Sourcepub async fn save_async(&self) -> Result<(), AgeVaultError>
pub async fn save_async(&self) -> Result<(), AgeVaultError>
Async variant of save — offloads blocking I/O to a spawn_blocking thread.
Use this when calling from an async context to avoid stalling the tokio executor.
§Errors
Returns AgeVaultError on encryption or write failure, or if the blocking task panics.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let mut vault = AgeVaultProvider::load(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
)?;
vault.set_secret_mut("MY_TOKEN".into(), "tok_abc123".into(), false)?;
vault.save_async().await?;Sourcepub fn set_secret_mut(
&mut self,
key: String,
value: String,
overwrite: bool,
) -> Result<(), AgeVaultError>
pub fn set_secret_mut( &mut self, key: String, value: String, overwrite: bool, ) -> Result<(), AgeVaultError>
Insert or update a secret in the in-memory map.
Refuses to replace an existing key unless overwrite is true, so that callers cannot
silently destroy a previously-stored secret by accident — see #5955 (and the sibling
incident #5874, which hit the same gap in the zeph init durable-execution wizard before
this guard existed at the vault layer). Callers that intend an unconditional update (e.g.
OAuth token refresh) pass overwrite: true explicitly.
Call save afterwards to persist the change to disk.
§Errors
Returns AgeVaultError::AlreadyExists if key is already present and overwrite is
false. The in-memory map is left untouched in that case.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let mut vault = AgeVaultProvider::load(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
)?;
vault.set_secret_mut("API_KEY".into(), "sk-...".into(), false)?;
vault.save()?;Sourcepub fn remove_secret_mut(&mut self, key: &str) -> bool
pub fn remove_secret_mut(&mut self, key: &str) -> bool
Remove a secret from the in-memory map.
Returns true if the key existed and was removed, false if it was not present.
Call save afterwards to persist the removal to disk.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let mut vault = AgeVaultProvider::load(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
)?;
let removed = vault.remove_secret_mut("OLD_KEY");
if removed {
vault.save()?;
}Sourcepub fn list_keys(&self) -> Vec<&str>
pub fn list_keys(&self) -> Vec<&str>
Return sorted list of secret keys (no values exposed).
Keys are returned in ascending lexicographic order. Secret values are never included.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let vault = AgeVaultProvider::load(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
)?;
for key in vault.list_keys() {
println!("{key}");
}Sourcepub fn get(&self, key: &str) -> Option<&str>
pub fn get(&self, key: &str) -> Option<&str>
Look up a secret value by key, returning None if not present.
Returns a borrowed &str tied to the lifetime of the vault. For async use across await
points, use VaultProvider::get_secret instead, which returns an owned String.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
let vault = AgeVaultProvider::load(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
)?;
match vault.get("ZEPH_OPENAI_API_KEY") {
Some(key) => println!("key length: {}", key.len()),
None => println!("key not configured"),
}Sourcepub fn init_vault(dir: &Path) -> Result<(), AgeVaultError>
pub fn init_vault(dir: &Path) -> Result<(), AgeVaultError>
Generate a new x25519 keypair, write the key file (mode 0600), and create an empty encrypted vault.
Creates dir and all missing parent directories before writing files. Existing files
are not checked — calling this on an already-initialised directory will overwrite both
the key and the vault, making the old key irrecoverable.
§Output files
| File | Contents | Unix mode |
|---|---|---|
<dir>/vault-key.txt | age identity (private + public key comment) | 0600 |
<dir>/secrets.age | age-encrypted empty JSON object {} | default |
Refuses to overwrite a pre-existing vault at dir — see AgeVaultProvider::init_vault_at
for the underlying guard and a force escape hatch.
§Errors
Returns AgeVaultError::VaultAlreadyExists if vault-key.txt or secrets.age already
exists under dir, or AgeVaultError on key/vault write failure or encryption failure.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
AgeVaultProvider::init_vault(Path::new("/etc/zeph"))?;
// /etc/zeph/vault-key.txt and /etc/zeph/secrets.age are now ready.Sourcepub fn init_vault_at(
key_path: &Path,
vault_path: &Path,
force: bool,
) -> Result<(), AgeVaultError>
pub fn init_vault_at( key_path: &Path, vault_path: &Path, force: bool, ) -> Result<(), AgeVaultError>
Generates a fresh age keypair and an empty encrypted vault at explicit key_path and
vault_path locations, mirroring AgeVaultProvider::load’s explicit-path signature.
Unlike AgeVaultProvider::init_vault (which always derives the standard
vault-key.txt/secrets.age filenames from a directory), this accepts arbitrary target
paths — the correct entry point when the caller has resolved --vault-key/--vault-path
CLI overrides that may not follow the default directory/filename convention.
§Overwrite guard
If either key_path or vault_path already exists and force is false, the vault is
left untouched and AgeVaultError::VaultAlreadyExists is returned — a partial prior
state (only one of the two files present) is treated the same as a full prior vault,
since it is itself evidence of an earlier init attempt worth protecting. Pass force: true to regenerate the keypair and overwrite both files unconditionally.
The existence check and the subsequent write are not wrapped in a single filesystem lock, so a racing concurrent call between the check and the write could still both pass the guard; this is a best-effort, not a hard mutual-exclusion guarantee.
§Errors
Returns AgeVaultError::VaultAlreadyExists when a vault already exists and force is
false, or AgeVaultError on key/vault write failure or encryption failure.
§Examples
use std::path::Path;
use zeph_vault::AgeVaultProvider;
AgeVaultProvider::init_vault_at(
Path::new("/etc/zeph/vault-key.txt"),
Path::new("/etc/zeph/secrets.age"),
false,
)?;Trait Implementations§
Source§impl Debug for AgeVaultProvider
impl Debug for AgeVaultProvider
Auto Trait Implementations§
impl Freeze for AgeVaultProvider
impl RefUnwindSafe for AgeVaultProvider
impl Send for AgeVaultProvider
impl Sync for AgeVaultProvider
impl Unpin for AgeVaultProvider
impl UnsafeUnpin for AgeVaultProvider
impl UnwindSafe for AgeVaultProvider
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request