vault-client-rs 0.8.0

A Rust client for the HashiCorp Vault HTTP API
Documentation

A Rust Client for the HashiCorp Vault HTTP API

Dual async and blocking Rust client for the HashiCorp Vault HTTP API.

Covers KV v1/v2, Transit, PKI, Database, SSH, Identity, TOTP, Cubbyhole, sys operations (seal/unseal, mounts, policies, leases, audit, Raft, rekey, namespaces, quotas), and authentication via Token, AppRole, Userpass, LDAP, Kubernetes, TLS certificates, GitHub, OIDC/JWT, AWS, Azure, GCP, Kerberos, and RADIUS.

Every API handler implements a trait (Kv2Operations, TransitOperations, PkiOperations, ...) so you can swap in a mock for tests without a running Vault server.

Project Maturity

This library is young. Before 1.0, breaking API changes likely, including major changes.

Requirements

  • Rust 1.93+
  • Tokio

Dependency

vault-client-rs = "0.7"

With Blocking Client

vault-client-rs = { version = "0.7", features = ["blocking"] }

With Automatic Token Renewal

vault-client-rs = { version = "0.7", features = ["auto-renew"] }

Async Client

Create a Client

use vault_client_rs::VaultClient;

let client = VaultClient::new("https://vault.example.com:8200", "hvs.EXAMPLE")?;

Using ClientBuilder

use vault_client_rs::VaultClient;

let client = VaultClient::builder()
    .address("https://vault.example.com:8200")
    .token_str("hvs.EXAMPLE")
    .max_retries(3)
    .build()?;

Circuit Breaker

Circuit Breaking is a feature that monitors consecutive failures and short-circuits (avoids) subsequent requests until a certain period of time passes:

use vault_client_rs::{VaultClient, CircuitBreakerConfig};

let client = VaultClient::builder()
    .address("https://vault.example.com:8200")
    .token_str("hvs.EXAMPLE")
    .circuit_breaker(CircuitBreakerConfig::default())
    .build()?;

CLI Mode

For short-lived sessions in CLI tools, cli_mode disables retries and sealed-Vault retry loops:

use vault_client_rs::VaultClient;

let client = VaultClient::builder()
    .address("https://vault.example.com:8200")
    .token_str("hvs.EXAMPLE")
    .cli_mode(true)
    .build()?;

From Environment Variables

Reads VAULT_ADDR, VAULT_TOKEN, VAULT_NAMESPACE, and other VAULT_* variables. When VAULT_TOKEN is not set, falls back to ~/.vault-token (written by vault login):

use vault_client_rs::VaultClient;

let client = VaultClient::from_env()?;

KV v2 Secrets

let kv = client.kv2("secret");

// Write a secret
kv.write("myapp/config", &serde_json::json!({
    "db_host": "db.internal",
    "db_port": "5432"
})).await?;

// Read into a typed struct or a HashMap
let data: HashMap<String, String> = kv.read_data("myapp/config").await?;

// Read a single field
let host: String = kv.read_field("myapp/config", "db_host").await?;

// List keys
let keys: Vec<String> = kv.list("myapp/").await?;

// Delete
kv.delete("myapp/config").await?;

KV v2 Version Management

let kv = client.kv2("secret");

// Read a specific version
let v1: KvReadResponse<MyStruct> = kv.read_version("myapp/config", 1).await?;

// Soft-delete versions
kv.delete_versions("myapp/config", &[1, 2]).await?;

// Undelete
kv.undelete_versions("myapp/config", &[1]).await?;

// Permanently destroy versions
kv.destroy_versions("myapp/config", &[2]).await?;

// Check-and-set write (optimistic locking)
kv.write_cas("myapp/config", &new_data, 3).await?;

// Patch (merge fields into existing secret)
kv.patch("myapp/config", &serde_json::json!({"new_field": "value"})).await?;

KV v1 Secrets

let kv = client.kv1("secret");

// Read into a typed struct or a HashMap
let data: HashMap<String, String> = kv.read_data("myapp/config").await?;

// Read a single field
let host: String = kv.read_field("myapp/config", "db_host").await?;

// Write, list, delete work the same way as in the KV v2 interface
kv.write("myapp/config", &serde_json::json!({"db_host": "db.internal"})).await?;

Transit Encryption

use secrecy::SecretString;

let transit = client.transit("transit");

// Encrypt, decrypt
let ciphertext = transit.encrypt("my-key", &SecretString::from("sensitive data")).await?;
let plaintext = transit.decrypt("my-key", &ciphertext).await?;

// Sign, verify
let signature = transit.sign("my-key", b"message", &Default::default()).await?;
let valid = transit.verify("my-key", b"message", &signature).await?;

// Key management
transit.create_key("my-key", &TransitKeyParams::default()).await?;
transit.rotate_key("my-key").await?;
let keys = transit.list_keys().await?;

PKI Certificates

use vault_client_rs::PkiIssueParams;

let pki = client.pki("pki");

// Issue a certificate
let cert = pki.issue("web-server", &PkiIssueParams {
    common_name: "app.example.com".into(),
    ttl: Some("24h".into()),
    ..Default::default()
}).await?;

// Sign a CSR
let signed = pki.sign("web-server", &PkiSignParams {
    common_name: "app.example.com".into(),
    csr: csr_pem.into(),
    ..Default::default()
}).await?;

// Revoke by serial
pki.revoke(&cert.serial_number).await?;

Database Dynamic Credentials

let db = client.database("database");

// Get dynamic credentials for a role
let creds = db.get_credentials("my-role").await?;
println!("username: {}", creds.username.expose_secret());

SSH Certificate Signing

use vault_client_rs::SshSignRequest;

let ssh = client.ssh("ssh");

let signed = ssh.sign_key("my-role", &SshSignRequest {
    public_key: public_key_string.into(),
    ..Default::default()
}).await?;

TOTP (Time-Based One-Time Passwords)

use vault_client_rs::TotpKeyRequest;

let totp = client.totp("totp");

// Create a key (Vault generates the secret)
totp.create_key("my-app", &TotpKeyRequest {
    generate: true,
    issuer: Some("MyApp".into()),
    account_name: Some("alice@example.com".into()),
    ..Default::default()
}).await?;

// Generate a code
let code = totp.generate_code("my-app").await?;

// Validate a code from a user
let result = totp.validate_code("my-app", "123456").await?;
assert!(result.valid);

Lease Management

let sys = client.sys();

// Look up a lease
let info = sys.read_lease("database/creds/my-role/abc123").await?;

// Renew a lease with an optional increment
sys.renew_lease("database/creds/my-role/abc123", Some("1h")).await?;

// Revoke a specific lease
sys.revoke_lease("database/creds/my-role/abc123").await?;

// Revoke all leases under a prefix
sys.revoke_prefix("database/creds/my-role").await?;

Automatic Token and Lease Renewal

Requires the auto-renew feature. The daemon renews the client token at ~2/3 of its remaining TTL and falls back to re-authentication if renewal fails:

// Renew the client token in the background
let daemon = client.start_token_renewal();

// Watch a dynamic lease (e.g. database credentials)
use std::time::Duration;
let watcher = client.watch_lease(lease_id, Duration::from_secs(3600));

// Or get programmatic events on each renewal or failure
let mut watcher = client.watch_lease_events(lease_id, Duration::from_secs(3600));
while let Some(event) = watcher.recv().await {
    match event {
        LeaseEvent::Renewed { ttl, .. } => println!("renewed, new TTL: {ttl:?}"),
        LeaseEvent::Expired { lease_id, .. } => {
            eprintln!("lease {lease_id} expired");
            break;
        }
        _ => {}
    }
}

// Both handles cancel their background task on drop

Authentication

use secrecy::SecretString;
use vault_client_rs::VaultClient;

let client = VaultClient::builder()
    .address("https://vault.example.com:8200")
    .build()?;

// Userpass
let auth = client.auth().userpass().login("alice", &SecretString::from("password")).await?;

// AppRole
let auth = client.auth().approle().login("role-id", &SecretString::from("secret-id")).await?;

// Kubernetes (in-cluster)
let auth = client.auth().kubernetes().login("my-role", &jwt).await?;

// LDAP
let auth = client.auth().ldap().login("alice", &SecretString::from("password")).await?;

// GitHub
let auth = client.auth().github().login(&SecretString::from("ghp_TOKEN")).await?;

// OIDC/JWT
let auth = client.auth().oidc().login_jwt("my-role", &jwt).await?;

Token Management

let token = client.auth().token();

let info = token.lookup_self().await?;
token.renew_self(Some("1h")).await?;

let child = token.create(&TokenCreateRequest {
    policies: Some(vec!["my-policy".into()]),
    ttl: Some("4h".into()),
    ..Default::default()
}).await?;

System Operations

let sys = client.sys();

let health = sys.health().await?;
let status = sys.seal_status().await?;
let policies = sys.list_policies().await?;

// Mounts
let mounts = sys.list_mounts().await?;
sys.mount("my-kv", &MountParams {
    mount_type: "kv".into(),
    options: Some([("version".into(), "2".into())].into()),
    ..Default::default()
}).await?;

// Policies
sys.write_policy("my-policy", r#"path "secret/*" { capabilities = ["read"] }"#).await?;

// Response wrapping
let wrapped: MyStruct = sys.unwrap(&wrap_token).await?;

Namespaces (Enterprise)

// Work in a specific namespace
let ns_client = client.with_namespace("my-team");
let kv = ns_client.kv2("secret");

Response Wrapping

// Wrap the next response with a TTL
let wrapping_client = client.with_wrap_ttl("5m");

Blocking Client

The blocking client has the same API as the async client but without async/await.

Create a Client

use vault_client_rs::blocking::BlockingVaultClient;

let client = BlockingVaultClient::new("https://vault.example.com:8200", "hvs.EXAMPLE")?;

Read a Secret

let data: std::collections::HashMap<String, String> =
    client.kv2("secret").read_data("myapp/config")?;

System Operations

let health = client.sys().health()?;
let policies = client.sys().list_policies()?;

Mocking in Tests

Every handler implements a trait (Kv2Operations, TransitOperations, PkiOperations, ...) that can be used for mocking in tests:

use vault_client_rs::{Kv2Operations, VaultError};
use vault_client_rs::types::kv::KvReadResponse;

struct MockKv2;

impl Kv2Operations for MockKv2 {
    async fn read<T: serde::de::DeserializeOwned + Send>(
        &self,
        _path: &str,
    ) -> Result<KvReadResponse<T>, VaultError> {
        todo!("return test data")
    }
    // ...
}

Copyright

(c) 2025-2026 Michael S. Klishin and Contributors.

License

This crate, vault-client-rs, is dual-licensed under the Apache Software License 2.0 and the MIT license.

SPDX-License-Identifier: Apache-2.0 OR MIT