{"id":50983993,"url":"https://github.com/valkyoth/openbao-rust-crate","last_synced_at":"2026-06-19T17:04:00.552Z","repository":{"id":360737972,"uuid":"1251497986","full_name":"valkyoth/openbao-rust-crate","owner":"valkyoth","description":"Secure, typed, async Rust SDK for OpenBao.","archived":false,"fork":false,"pushed_at":"2026-06-03T19:40:09.000Z","size":2467,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-03T20:07:49.760Z","etag":null,"topics":["async","certificates","hacktoberfest","keys","openbao","rust","rust-crate","rust-lang","rustlang","sdk","secrets","security"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/openbao","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/valkyoth.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["eldryoth"],"thanks_dev":"u/gh/eldryoth"}},"created_at":"2026-05-27T16:30:31.000Z","updated_at":"2026-06-03T19:40:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/valkyoth/openbao-rust-crate","commit_stats":null,"previous_names":["valkyoth/openbao-rust-crate"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/valkyoth/openbao-rust-crate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fopenbao-rust-crate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fopenbao-rust-crate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fopenbao-rust-crate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fopenbao-rust-crate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/valkyoth","download_url":"https://codeload.github.com/valkyoth/openbao-rust-crate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fopenbao-rust-crate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34540570,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-19T02:00:06.005Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","certificates","hacktoberfest","keys","openbao","rust","rust-crate","rust-lang","rustlang","sdk","secrets","security"],"created_at":"2026-06-19T17:03:59.397Z","updated_at":"2026-06-19T17:04:00.542Z","avatar_url":"https://github.com/valkyoth.png","language":"Rust","funding_links":["https://github.com/sponsors/eldryoth","https://thanks.dev/u/gh/eldryoth"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cb\u003eSecure, typed, async Rust SDK for OpenBao.\u003c/b\u003e\u003cbr\u003e\n  Memory-safe by default. Minimal dependency surface. Built for audited secret workflows.\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://openbao.org/\"\u003eOpenBao\u003c/a\u003e\n  ·\n  \u003ca href=\"docs/OPENBAO_API_COVERAGE.md\"\u003eAPI Coverage\u003c/a\u003e\n  ·\n  \u003ca href=\"docs/RELEASE_PLAN.md\"\u003eRelease Plan\u003c/a\u003e\n  ·\n  \u003ca href=\"docs/API_STABILITY_AUDIT.md\"\u003eAPI Stability\u003c/a\u003e\n  ·\n  \u003ca href=\"docs/MIGRATION_GUIDE.md\"\u003eMigration\u003c/a\u003e\n  ·\n  \u003ca href=\"SECURITY.md\"\u003eSecurity\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/valkyoth/openbao-rust-crate/main/.github/images/openbao_rust_crate.webp\" alt=\"OpenBao Rust crate overview\"\u003e\n\u003c/p\u003e\n\n# OpenBao Rust SDK\n\n`openbao` is a secure, typed, async Rust SDK for\n[OpenBao](https://openbao.org/), the community-driven open source fork of\nVault. It is designed for audited secret workflows: HTTPS by default, no\nredirect forwarding, strict path validation, secret-aware token types, and a\nsmall reviewed dependency surface.\n\nThe crate name on crates.io is `openbao`; Rust imports are lowercase:\n\n```rust\nuse openbao::Client;\n```\n\nThis README documents the stable `1.0.x` API. The current patch line is\n`1.0.2`, which preserves the `1.0.0` public API and updates dependencies and\nrelease tooling.\n\nThe crate is dual-licensed under MIT or Apache-2.0.\n\n## Current Status\n\nImplemented now:\n\n- Async client with typestate authentication.\n- Direct token authentication with re-exported `openbao::SecretString`.\n- AppRole login plus role and SecretID administration, with role IDs,\n  SecretIDs, accessors, and returned tokens treated as secret material.\n- Kubernetes auth login plus config and role administration helpers.\n- TLS certificate auth login, method config, CA role, and CRL administration\n  helpers.\n- JWT login plus JWT/OIDC auth method config, role administration, browser\n  authorization URL, callback, and direct/device polling helpers.\n- LDAP auth login plus config and user/group policy mapping helpers.\n- RADIUS login plus config and user policy mapping helpers.\n- Kerberos login plus service-account, LDAP config, and group policy mapping\n  helpers.\n- Userpass login plus user create/read/list/delete, password update, and\n  policy update helpers.\n- Token create, create-orphan, role create/read/list/delete, lookup, accessor\n  lookup/list/renew/revoke, renew, revoke, revoke-orphan, revoke-self, and tidy\n  helpers.\n- KV v2 read, write, CAS write, patch, list, latest delete, version read,\n  version delete, undelete, destroy, metadata, backend config, typed data, and\n  secret-aware service config read/write helpers.\n- KV v1 read, write, delete, and list helpers.\n- Cubbyhole read, optional read, write, delete, and list helpers for\n  token-scoped handoff data.\n- Kubernetes secrets engine config, role create/read/list/delete, and\n  service account credential generation helpers.\n- RabbitMQ secrets engine connection config, lease config, role\n  create/read/list/delete, and dynamic credential helpers.\n- Database connection config, dynamic roles, static roles, root/static\n  rotation, and credential helpers.\n- Identity entity, group, entity-alias, and group-alias lifecycle, lookup,\n  entity merge, OIDC token backend config, signing key CRUD/rotate, role CRUD,\n  signed ID token generation, token introspection, discovery, JWKS, OIDC\n  provider/scope/client/assignment admin, and named-provider discovery/JWKS\n  helpers.\n- LDAP secrets engine config, static role, dynamic role, credential, library\n  checkout, and check-in helpers.\n- SSH role, zero-address role, IP lookup, OTP credential, issuer config,\n  issuer list/submit/read/update/delete, CA public-key metadata, CA sign,\n  generated certificate/key issue, and OTP verification helpers.\n- TOTP key create/read/list/delete, code generation, and code validation\n  helpers.\n- PKI URL and CRL config, default issuer/key config, root/intermediate\n  generation, root rotate/replace, intermediate signing and install,\n  multi-issuer issue/sign flows, role write/read/list/delete/patch, CEL roles,\n  issue, sign, revoke, revoke-with-key, certificate list/read, issuer/key\n  list/read/delete/update, issuer revoke, CA/key import, ACME config/EAB and\n  directory URL helpers, CRL and delta-CRL management, tidy, tidy status, and\n  tidy cancel helpers.\n- Transit key create, read, list, delete, config update, rotate, export, BYOK\n  wrapping-key/import/import-version/export helpers, soft-delete/restore,\n  cache/global config, CSR generation, certificate-chain install, backup,\n  restore, trim, encrypt/decrypt/rewrap batch helpers, data key, random, hash,\n  HMAC, sign/verify batch helpers, typed RSA/JWS signing options, optional\n  raw-byte helpers, and gated software import-wrapping helpers.\n- System health, readiness polling, seal status, leader status, OpenAPI\n  discovery, JSON metrics, runtime logger level, version history, namespace\n  management, rate-limit quota management, password policies, resultant ACL\n  inspection, and loopback-only dev bootstrap helpers.\n- Secret and auth mount enable, list, read, tune, and disable helpers.\n- Response wrapping lookup, wrap, unwrap, and rewrap helpers.\n- Typed response wrapping through `Client::wrapping`, `WrappingContext`, and\n  `WrappedResponse\u003cT\u003e`.\n- ACL policy list, read, write, delete, and prefix list helpers.\n- Bounded ACL policy builder helpers for common KV v2 and Transit\n  least-privilege rules, including response-wrapping TTL constraints.\n- Idempotent admin bootstrap plan builder for KV v2 mounts, Transit mounts,\n  Transit keys, ACL policies, KV v2 string secret values, auth methods,\n  AppRole roles, PKI/database/SSH mount and role convergence, explicit scoped\n  service-token issuance, and explicit AppRole SecretID issuance.\n- Capability checks for the caller token, an explicit token, or a token\n  accessor.\n- Audit device list, enable, disable, and hash helpers.\n- Safe exact lease lookup, renew, revoke, prefix revoke, force prefix revoke,\n  and lease count helpers.\n- Plugin catalog list, type-list, register, read, delete, and backend reload\n  helpers.\n- Explicitly gated production init, unseal, seal, rekey, key-share rotation,\n  keyring rotation, root/recovery-token generation, decode-token, legacy\n  recovery-key rekey, and in-flight request inspection operator APIs.\n- Environment-based client construction from common OpenBao/Vault variables.\n- Shared authenticated client and Rust `Duration` to OpenBao duration string\n  helpers for async application ergonomics.\n- Explicit retry/backoff helpers for caller-approved idempotent raw requests.\n- Bootstrap read-only preview, report lookup helpers for issued credentials,\n  and changed steps.\n- Best-effort FIPS-oriented posture reporting for crate-visible Transit and\n  deployment assumptions; this is advisory and not a certification claim.\n- Shared `ListEntries` ergonomics for common list responses without changing\n  their documented fields.\n- Optional RFC3339 timestamp parsing helpers behind the `time` feature.\n- Optional `tracing` and HTTP/2 features without default dependency or runtime\n  transport hooks.\n- Raw JSON request escape hatch for endpoints that are not typed yet.\n- Operator-gated raw storage read, write, list, and delete helpers.\n- Operator-gated pprof diagnostic byte helpers.\n- Typed custom plugin wrapper pattern documentation and safe building blocks\n  for application-specific OpenBao plugin APIs.\n- Local TLS OpenBao Podman stack on `9940` and `9941`.\n- Real OpenBao integration test gate using the pinned OpenBao image.\n\n`1.0.x` is a stable maintenance line. Feature history and patch details live in\n[CHANGELOG.md](CHANGELOG.md) and [release-notes](release-notes).\n\nSee [API Coverage](docs/OPENBAO_API_COVERAGE.md) and\n[Release Plan](docs/RELEASE_PLAN.md) for the stable support policy.\n\n## Trust Dashboard\n\n| Area | Status |\n| --- | --- |\n| License | `MIT OR Apache-2.0` |\n| Rust edition | 2024 |\n| MSRV | Rust `1.90.0` |\n| Async runtime | Runtime-agnostic client; examples use Tokio |\n| HTTP transport | `reqwest` with redirects disabled |\n| Default TLS backend | Rustls |\n| TLS floor | TLS 1.3 by default; TLS 1.2 requires explicit opt-in |\n| Plain HTTP | Rejected by default; sensitive requests still require HTTPS |\n| Token storage | `openbao::SecretString` (`secrecy::SecretString`) |\n| Unsafe policy | `unsafe_code = \"forbid\"` |\n| Path validation | Rejects traversal, query/fragment injection, empty segments, controls, and trailing periods |\n| Error posture | API error strings are bounded and sanitized before formatting |\n| Dependency policy | `cargo deny` plus RustSec audit in the release gate |\n| Release evidence | fmt, clippy, tests, docs, deny, audit, SBOM, and real OpenBao integration |\n| Pentest gate | Required before tagging a release |\n\nSecurity details live in [SECURITY.md](SECURITY.md). Release evidence and\nrelease sequencing live in [release-notes](release-notes) and\n[docs/RELEASE_PLAN.md](docs/RELEASE_PLAN.md).\n\n## Rust Version Support\n\nThe minimum supported Rust version is Rust `1.90.0`. New deployments should\nprefer the latest stable Rust; as of June 1, 2026, that is Rust `1.96.0`.\n\nThe `1.0.x` release line tracks compatibility evidence across this supported\nrange:\n\n| Rust | Required Evidence |\n| --- | --- |\n| `1.90.0` | Full test suite and clippy. |\n| `1.91.0` | `cargo check --all-features`. |\n| `1.92.0` | `cargo check --all-features`. |\n| `1.93.0` | `cargo check --all-features`. |\n| `1.94.0` | `cargo check --all-features`. |\n| `1.95.0` | `cargo check --all-features`. |\n| `1.96.0` | `cargo check --all-features`. |\n\n## Install\n\n```toml\n[dependencies]\nopenbao = \"1\"\nserde = { version = \"1.0.228\", features = [\"derive\"] }\ntokio = { version = \"1.52.3\", features = [\"macros\", \"rt-multi-thread\", \"time\"] }\n```\n\nSome advanced examples below use JSON helper types directly:\n\n```toml\n[dependencies]\nserde_json = \"1.0.150\"\n```\n\n## Quick Start\n\nRead a KV v2 secret using the secure environment-based constructor:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\nuse serde::Deserialize;\n\n#[derive(Deserialize)]\nstruct DbCredentials {\n    username: String,\n    password: SecretString,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    // Reads OPENBAO_ADDR/BAO_ADDR/VAULT_ADDR plus token, namespace, and CA aliases.\n    let client = Client::from_env_with_token()?;\n    let secret = client\n        .kv2(\"secret\")?\n        .read::\u003cDbCredentials\u003e(\"production/database\")\n        .await?;\n\n    println!(\"loaded credentials for {}\", secret.data.username);\n    let _password = secret.data.password;\n    Ok(())\n}\n```\n\nThe crate defaults to the common SDK surface:\n\n```toml\n[dependencies]\nopenbao = { version = \"1\", features = [\"approle\", \"cert-auth\", \"cubbyhole\", \"database\", \"jwt-auth\", \"kubernetes-auth\", \"ldap-auth\", \"kubernetes\", \"userpass\", \"token\", \"kv1\", \"kv2\", \"pki\", \"ssh\", \"totp\", \"transit\", \"sys\", \"rustls-tls\"] }\n```\n\nFor a smaller build, disable defaults and opt into only what the application\nuses:\n\n```toml\n[dependencies]\nopenbao = { version = \"1\", default-features = false, features = [\"kv2\", \"sys\", \"rustls-tls\"] }\n```\n\nOptional RFC3339 timestamp parsing is available behind the lightweight `time`\nfeature:\n\n```toml\n[dependencies]\nopenbao = { version = \"1\", features = [\"time\"] }\n```\n\n## Features\n\n| Feature | Default | Purpose |\n| --- | --- | --- |\n| `approle` | yes | AppRole login, role, delegated role-property, RoleID, and SecretID helpers. |\n| `cert-auth` | yes | TLS certificate auth login/config/role/CRL helpers. |\n| `cubbyhole` | yes | Token-scoped Cubbyhole read/write/delete/list helpers. |\n| `database` | yes | Database secrets engine config, role, credential, and rotation helpers. |\n| `identity` | yes | Identity entity, group, entity-alias, and group-alias helpers. |\n| `jwt-auth` | yes | JWT login plus JWT/OIDC config, role administration, auth URL, callback, and poll helpers. |\n| `kerberos-auth` | yes | Kerberos SPNEGO login, service-account config, LDAP config, and group mapping helpers. |\n| `kubernetes-auth` | yes | Kubernetes auth login/config/role helpers. |\n| `ldap-auth` | yes | LDAP auth login/config/user/group mapping helpers. |\n| `radius-auth` | no | RADIUS login/config/user mapping helpers. Legacy RADIUS uses MD5-based authenticators and requires `radius-auth-acknowledged`; do not use it for classified networks or new high-assurance deployments. |\n| `radius-auth-acknowledged` | no | Explicit acknowledgment for audited legacy RADIUS compatibility builds. |\n| `kubernetes` | yes | Kubernetes secrets engine config, role, and generated service account token helpers. |\n| `ldap` | yes | LDAP secrets engine config, static/dynamic role, credential, and library helpers. |\n| `rabbitmq` | yes | RabbitMQ secrets engine connection, lease, role, and credential helpers. |\n| `userpass` | yes | Userpass login and user administration helpers. |\n| `token` | yes | Token lifecycle, create-orphan, accessor renewal/revocation, token role, tidy, and revoke-orphan helpers. |\n| `kv1` | yes | KV v1 secrets engine helpers. |\n| `kv2` | yes | KV v2 secrets engine helpers. |\n| `pki` | yes | PKI authority, issuer/key metadata/import, role, role patch, issue/sign, revoke, cert read/list, ACME config/EAB/directory URL, CRL config/rotate, tidy, tidy status, and tidy cancel helpers. |\n| `ssh` | yes | SSH roles, OTP credentials, issuer management, CA sign/issue, issuer config, and OTP verification helpers. |\n| `totp` | yes | TOTP key and code helpers. |\n| `transit` | yes | Transit key lifecycle, batch cryptography, and single-operation cryptography helpers. |\n| `transit-bytes` | no | Raw-byte Transit convenience helpers using `base64-ng` for OpenBao's base64 request/response fields. |\n| `transit-import` | no | Software AES-KWP/RSA-OAEP helper for preparing OpenBao Transit BYOK import blobs. Requires `transit-import-acknowledged`; uses `openssl` and `aes-kw`; requires an audited OpenSSL 1.1.1+ runtime baseline; not an HSM, FIPS, certification, post-quantum, or security-boundary claim. Do not use it for classified or high-assurance key wrapping. |\n| `transit-import-acknowledged` | no | Explicit acknowledgment that Transit BYOK software wrapping passes key material through software memory and OpenSSL-managed heap. |\n| `sys` | yes | System backend, readiness, leases, quotas, password policies, resultant ACL, storage, diagnostics, and operator-gated helpers. |\n| `http2` | no | Enables reqwest HTTP/2 support. ALPN negotiates HTTP/2 when OpenBao supports it and otherwise falls back to HTTP/1.1. |\n| `time` | no | Optional RFC3339 timestamp parsing helpers using the `time` crate. |\n| `tokio-helpers` | no | Enables Tokio convenience helpers such as `Sys::wait_until_unsealed`. Runtime-neutral variants remain available without this feature. |\n| `tracing` | no | Optional request/response instrumentation with method, redacted path shape, and status only. No bodies, tokens, or namespaces are logged; path shapes still reveal operational activity, so strict path-confidentiality deployments should suppress debug `openbao.request` spans, for example with `EnvFilter::new(\"openbao=info\")`. No OpenTelemetry SDK dependency. |\n| `tls12-acknowledged` | no | Explicit acknowledgment for legacy TLS 1.2 compatibility. TLS 1.3 remains the default and is strongly preferred for high-security OpenBao deployments. |\n| `allow-sha1-acknowledged` | no | Explicit opt-in for legacy Transit SHA-1 selection. Disabled by default. |\n| `allow-weak-jitter-fallback-acknowledged` | no | Explicit acknowledgment for using a timing-based retry jitter fallback if OS randomness fails. Default builds skip jitter rather than use the weak fallback. |\n| `rustls-tls` | yes | Rustls transport configuration. |\n| `native-tls` | no | Legacy native TLS support. Requires `native-tls-acknowledged` after audit. |\n| `native-tls-acknowledged` | no | Explicit acknowledgment for audited native TLS builds. |\n| `sensitive-http-test-only` | no | Hidden test escape hatch for this crate's loopback HTTP mock tests. Requires `sensitive-http-test-only-acknowledged`; never enable in application builds. |\n| `sensitive-http-test-only-acknowledged` | no | Explicit acknowledgment for this crate's audited loopback HTTP test harness. |\n| `operator-ops` | no | Production init, unseal, seal, rekey, key-share rotate, keyring rotate, raw storage, and destructive PKI root deletion APIs. Requires `operator-ops-acknowledged`. |\n| `operator-ops-acknowledged` | no | Explicit acknowledgment for audited operator-operation builds. |\n\n## Support Matrix\n\nThe detailed OpenBao `2.5.x` endpoint-by-endpoint coverage matrix is tracked\nin [docs/OPENBAO_2_5_ENDPOINT_MATRIX.md](docs/OPENBAO_2_5_ENDPOINT_MATRIX.md).\nFor the stable `1.0.x` line it records `643` documented endpoint rows, with\n`597/643` (`92.8%`) strict typed or operator-gated coverage. All rows are now\naddressed by typed, operator-gated, partial, external, or rejected policy, with\nzero `planned` and zero `decision` rows.\n\n### Client, Transport, And TLS\n\n| Capability | Status | Notes |\n| --- | --- | --- |\n| Async client | Yes | Built on `reqwest` with a small public API surface. |\n| Typestate auth | Yes | Separate unauthenticated and authenticated client states. |\n| HTTPS by default | Yes | Plain HTTP is rejected unless loopback HTTP is explicitly enabled. |\n| Redirect protection | Yes | Redirect following is disabled to avoid forwarding token headers. |\n| Response size cap | Yes | 32 MiB default with per-client lowering for small-response workflows. |\n| Timestamp parsing | Optional | Enable `time` for RFC3339 parsing helpers without changing response field types. |\n| TLS floor | Yes | TLS 1.3 minimum by default; audited legacy deployments can opt down to TLS 1.2. |\n| HTTP protocol | HTTP/1.1 by default | Enable non-default `http2` for TLS ALPN HTTP/2 negotiation. No runtime HTTP/2 knob is exposed. |\n| Custom CA roots | Yes | Extra root certificates can be merged with the platform trust store. |\n| Root-only trust stores | Yes | System roots can be bypassed by using only configured root certificates. This is the supported alternative to leaf certificate or SPKI pinning. |\n| Client TLS identity | Yes | Optional mutual TLS client identity for TLS certificate auth. |\n| Connection timeout | Yes | 5-second connection timeout by default; caller overrides are bounded. |\n| User agent fingerprinting | Yes | Default user agent omits the exact crate version. |\n| Namespace header | Yes | `X-Vault-Namespace` support for namespace-aware deployments. |\n| Environment construction | Yes | Reads `OPENBAO_*`, `BAO_*`, and `VAULT_*` aliases with secure defaults. |\n| Raw JSON requests | Yes | Escape hatch for endpoints that are not typed yet. |\n\n### Authentication\n\n| Capability | Status | Notes |\n| --- | --- | --- |\n| Direct token auth | Yes | Tokens are accepted as `SecretString`. |\n| `X-Vault-Token` | Yes | Default documented OpenBao-compatible token header. |\n| Bearer auth | Yes | Optional `Authorization: Bearer` header mode. |\n| AppRole login/admin | Yes | Role ID, SecretID, accessors, and returned tokens are secret-aware; role, delegated role-property, and SecretID lifecycle helpers are typed. |\n| Token accessor handling | Yes | Accessors are treated as secret material. |\n| Token lifecycle helpers | Yes | Lookup, accessor lookup/list, create/create-orphan, renew/renew-accessor, revoke, revoke-self, and revoke-accessor helpers. |\n| Kubernetes auth | Yes | Login, auth method config, and role administration helpers. |\n| TLS certificate auth | Yes | Login, auth method config, CA role administration, and CRL helpers. |\n| JWT/OIDC | Yes | JWT login plus JWT/OIDC auth method config, role administration, browser auth URL, callback, and direct/device poll helpers. |\n| LDAP auth | Yes | Login, method config, user/group create/read/list/delete policy mapping helpers. |\n| RADIUS auth | Gated | Login, method config, user create/read/list/delete, paginated user list helpers. Available only with `radius-auth` plus `radius-auth-acknowledged` because legacy RADIUS uses MD5-based authenticators. |\n| Kerberos auth | Yes | SPNEGO login, service-account/keytab config, Kerberos LDAP config, and group create/read/list/delete mapping helpers. |\n| Userpass auth | Yes | Login and user create/read/list/delete, password update, and policy update helpers. |\n\n### Secret Engines\n\n| Capability | Status | Notes |\n| --- | --- | --- |\n| KV v2 read/write | Yes | Typed serialization and deserialization. |\n| KV v2 CAS write | Yes | Optional check-and-set version support. |\n| KV v2 patch | Yes | JSON merge patch content type. |\n| KV v2 list/delete versions | Yes | Metadata list, latest delete, soft delete, undelete, and destroy. |\n| KV v2 metadata/config | Yes | Backend, per-key metadata, typed data, and secret-aware service config helpers. |\n| KV v1 | Yes | Read, write, delete, and list helpers. |\n| Cubbyhole | Yes | Token-scoped read, optional read, write, delete, and list helpers. |\n| Kubernetes secrets | Yes | Config, role create/read/list/delete, and generated service account token helpers. |\n| RabbitMQ secrets | Yes | Connection config, lease config, role create/read/list/delete, and generated credential helpers. |\n| Identity | Partial | Entity, group, entity-alias, and group-alias lifecycle helpers, entity/group lookup, entity merge, OIDC token backend config, signing key CRUD/rotate, role CRUD/list, signed ID token generation, token introspection, discovery, JWKS, OIDC provider/scope/client/assignment admin, named-provider discovery/JWKS, MFA method management, TOTP MFA generation/admin actions, and MFA login-enforcement helpers are implemented. Named-provider OIDC browser protocol flows stay external. |\n| LDAP secrets | Yes | Config, root rotation, static roles/credentials, dynamic roles/credentials, and library check-out/check-in helpers. |\n| Database credentials | Yes | Connection config/list/read/delete, dynamic roles/credentials, static roles/credentials, and root/static rotation helpers. |\n| Transit | Yes | Key create/read/list/delete/config update/rotate/export/backup/restore/trim, encrypt/decrypt/rewrap batch helpers, data key, random, hash, HMAC, sign/verify batch helpers, typed RSA/JWS signing options, optional raw-byte helpers, wrapping-key, import/import-version, BYOK export, soft-delete/restore, cache/global config, CSR generation, and certificate-chain install helpers. Import wrappers accept externally wrapped `SecretString` ciphertext or public-key-only import material; raw private or symmetric key bytes stay outside the default endpoint wrappers. The non-default `transit-import` plus `transit-import-acknowledged` features add a software AES-KWP/RSA-OAEP wrapping helper for audited development and automation use. |\n| PKI | Partial | Authority generation/signing/install, URL/CRL config, roles, role patch, issue, sign, named-issuer issue/sign, named-issuer sign-intermediate, revoke, revoke-with-key, revoked/revocation-queue/detailed certificate lists, issuer CRL resign, certificate list/read, issuer/key list/read/delete/update, issuer revoke, default issuer/key config, cluster config, auto-tidy config, root rotate/replace, standalone key generation, multi-issuer root/intermediate generation, operator-gated default root deletion with explicit confirmation, operator-gated sign-verbatim/sign-self-issued/cross-sign/sign-revocation-list, CEL role management and CEL issue/sign, current-doc field expansion for role/generation/CRL/tidy structs, CA/key import, ACME config/EAB/directory URL, CRL rotate, delta CRL rotate, tidy, tidy status, and tidy cancel are implemented. Unauthenticated public CA/CRL/cert and OCSP protocol reads stay external. |\n| TOTP | Yes | Key create/read/list/delete, code generation, and code validation helpers. |\n| SSH | Partial | Roles, zero-address roles, IP role lookup, OTP credentials, issuer config/list/submit/read/update/delete, authenticated CA public-key metadata, CA sign/issue, and OTP verification are implemented. Raw unauthenticated public-key reads are intentionally not typed. |\n| Custom plugin patterns | Yes | Documented wrapper pattern for typed plugin-specific APIs over `Client::request_json`. |\n\n### System Backend And Operations\n\n| Capability | Status | Notes |\n| --- | --- | --- |\n| Health | Yes | Accepts OpenBao active, standby, sealed, and uninitialized health statuses. |\n| Init status | Yes | Typed `/sys/init` status helper. |\n| Seal status | Yes | Typed `/sys/seal-status` helper. |\n| Leader status | Yes | Typed `/sys/leader` helper. |\n| HA status | Yes | Typed `/sys/ha-status` helper with bounded node lists. |\n| Key status | Yes | Typed `/sys/key-status` helper. |\n| OpenAPI discovery | Yes | Typed JSON helper for `/sys/internal/specs/openapi`. |\n| Internal UI helpers | Yes | Internal UI namespace and mount discovery helpers with bounded maps; OpenBao does not guarantee endpoint stability. |\n| Metrics | Yes | Typed JSON helper for `/sys/metrics?format=json` and capped Prometheus text helper. |\n| Host diagnostics | Yes | JSON helper for `/sys/host-info` platform diagnostics. |\n| Pprof diagnostics | Gated | Capped zeroizing byte helpers for `/sys/pprof/:profile`, available only with `operator-ops` plus `operator-ops-acknowledged`. |\n| Sanitized config state | Yes | JSON helper for `/sys/config/state/sanitized`. |\n| Audited request headers | Yes | List, read, write, and delete `/sys/config/auditing/request-headers` helpers. |\n| CORS config | Yes | Read, write, and delete `/sys/config/cors` helpers with bounded lists, header validation, and wildcard-origin rejection. |\n| Runtime loggers | Yes | Read, set, and reset transient `/sys/loggers` verbosity levels. |\n| Version history | Yes | Typed LIST helper for installed OpenBao version history. |\n| Namespaces | Yes | List, create, read, patch, and delete namespace helpers with local name validation. |\n| Rate-limit quotas | Yes | Global quota config plus named rate-limit quota list/create/read/delete helpers. |\n| Locked users | Yes | List all locked users, filter by mount accessor, and unlock aliases. |\n| Raft storage | Yes | Integrated Storage Raft join/configuration/peer/bootstrap, capped snapshot download/restore, and Autopilot JSON helpers; join helpers require HTTPS leader addresses. |\n| Remount | Yes | Start mount migrations and poll migration status. |\n| Step down | Gated | Active-node handoff helper for `/sys/step-down`, available only with `operator-ops` plus `operator-ops-acknowledged`. |\n| System tools | Yes | Random byte generation and hash helpers with bounded random requests and secret-aware outputs. |\n| Dev bootstrap | Yes | Fresh numeric-loopback dev instances only; not for production or HSM/KMS deployments. |\n| Mount management | Yes | Secret and auth mount enable/list/read/tune/disable helpers. |\n| Response wrapping | Yes | Lookup, wrap, unwrap, and rewrap helpers. |\n| Policies and capabilities | Yes | ACL policy read/write/list/delete, password policy list/read/write/delete/generate, bounded policy builder helpers, self/token/accessor capability checks, resultant ACL inspection, and typed capability views. |\n| MFA validation | Yes | Typed `/sys/mfa/validate` helper for MFA-enforced login flows with secret-aware passcodes, returned tokens, and accessors. |\n| Admin bootstrap | Yes | Idempotent plan builder, read-only preview, KV v2/Transit/PKI/database/SSH mounts, Transit keys, ACL policies, KV v2 string values, PKI/database/SSH/AppRole roles, Identity entities/groups, explicit token issuance, and explicit AppRole SecretID issuance. |\n| FIPS posture helper | Advisory | Best-effort report for crate-visible Transit choices and deployment assumptions. Does not certify OpenBao or the deployment. |\n| List ergonomics | Yes | `ListEntries` exposes `entries`, `iter`, `len`, `is_empty`, and `contains` for common string list responses. |\n| Audit devices | Yes | Enable, list, disable, and audit hash helpers. |\n| Lease helpers | Yes | Safe exact lookup, renew, revoke, prefix revoke, force prefix revoke, count, tidy, and `RenewalHint` timing helpers for caller-owned renewal loops. |\n| Plugin catalog | Yes | List, type-list, register, read, delete, and mounted backend reload helpers. |\n| Production init, unseal, rekey, rotate, token ceremonies, in-flight diagnostics, PKI root deletion | Gated | Available only with `operator-ops` plus `operator-ops-acknowledged`; default builds cannot call these APIs. PKI root deletion also requires `PkiRootDeletion::confirm()` at the call site. |\n| System backend closure | Yes | Password policies, root/recovery token ceremonies, decode-token, resultant ACL, legacy recovery-key rekey, and in-flight request inspection are implemented; config-ui, monitor streaming, internal router/request inspection, and internal counters are rejected for stable scope. |\n\n## Examples\n\nCreate a client from an existing token:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let health = client.sys().health().await?;\n    println!(\"openbao version: {}\", health.version);\n    Ok(())\n}\n```\n\nCreate an authenticated client from environment variables:\n\n```rust,no_run\nuse openbao::{Client, ListEntries, Result};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    // Reads OPENBAO_ADDR/BAO_ADDR/VAULT_ADDR plus token, namespace, and CA aliases.\n    let client = Client::from_env_with_token()?;\n\n    let health = client.sys().health().await?;\n    println!(\"openbao version: {}\", health.version);\n    Ok(())\n}\n```\n\nRetry an idempotent raw request with explicit exponential backoff:\n\n```rust,no_run\nuse std::time::Duration;\n\nuse openbao::{Client, Result, RetryPolicy, RetryableMethod};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let client = Client::from_env_with_token()?;\n    let policy = RetryPolicy::exponential(\n        3,\n        Duration::from_millis(100),\n        Duration::from_secs(2),\n    )?;\n\n    let health: openbao::sys::Health = client\n        .request_json_with_retry(\n            RetryableMethod::Get,\n            \"sys/health\",\n            Option::\u003c\u0026openbao::Empty\u003e::None,\n            policy,\n            tokio::time::sleep,\n        )\n        .await?;\n\n    println!(\"openbao version: {}\", health.version);\n    Ok(())\n}\n```\n\nRetry is never global. `RetryableMethod` exposes only GET, HEAD, and OpenBao\nLIST so the helper cannot retry write verbs by accident. Exponential retry\ndelays include OS-random bounded jitter by default to avoid\nsynchronized retry waves after temporary OpenBao outages.\n\nConfigure a stricter client with a namespace and root-only trust store:\n\nUse `only_root_certificates` when you want to trust only your internal OpenBao\nCA and reject every platform or public CA root. This is intentionally preferred\nover leaf certificate or public-key pinning because your CA can rotate server\ncertificates without requiring every client to update a pin. For a self-signed\nOpenBao listener certificate, pass that certificate as the sole trusted root.\nThe rustls-backed HTTP client does not perform OCSP or CRL checking for the\nserver certificate automatically. Static PEM CRLs can be configured with\n`add_certificate_revocation_list_pem` when using `only_root_certificates`;\ncallers remain responsible for refreshing CRLs and rebuilding clients before\nthey expire. Deployments that rely on revocation should still issue short-lived\nlistener certificates and enforce certificate-auth revocation server-side.\n\n```rust,no_run\nuse openbao::{Client, OpenBaoConfig, Result};\nuse openbao::Certificate;\nuse openbao::SecretString;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let ca_pem = std::fs::read(\"openbao-ca.pem\").map_err(|_| {\n        openbao::Error::InvalidTlsConfig(\n            \"failed to read the configured CA certificate file\".into(),\n        )\n    })?;\n    let crl_pem = std::fs::read(\"openbao-ca.crl\").map_err(|_| {\n        openbao::Error::InvalidTlsConfig(\n            \"failed to read the configured CRL file\".into(),\n        )\n    })?;\n    let ca = Certificate::from_pem(\u0026ca_pem)?;\n\n    let config = OpenBaoConfig::new(\"https://bao.example.com:8200\")?\n        .namespace(\"admin/team-a\")?\n        .only_root_certificates(vec![ca])?\n        .add_certificate_revocation_list_pem(\u0026crl_pem)?;\n\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::from_config(config)?.try_with_token(token)?;\n\n    let seal = client.sys().seal_status().await?;\n    println!(\"sealed: {}\", seal.sealed);\n    Ok(())\n}\n```\n\nThe environment equivalent for root-only trust is\n`OPENBAO_CACERT=/path/to/ca.pem` together with\n`OPENBAO_TLS_ROOTS_ONLY=true`; CRLs are configured through the explicit Rust\nAPI so callers can own refresh and client rebuild policy.\n\nAuthenticate with AppRole:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let client = Client::new(\"https://bao.example.com:8200\")?;\n    let role_id = SecretString::from(std::env::var(\"APPROLE_ROLE_ID\").unwrap_or_default());\n    let secret_id = SecretString::from(std::env::var(\"APPROLE_SECRET_ID\").unwrap_or_default());\n\n    let (client, login) = client.login_approle(role_id, secret_id).await?;\n    let health = client.sys().health().await?;\n\n    let _token_accessor = login.accessor;\n    println!(\"openbao version: {}\", health.version);\n    Ok(())\n}\n```\n\nAuthenticate with Userpass:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let client = Client::new(\"https://bao.example.com:8200\")?;\n    let password = SecretString::from(std::env::var(\"BAO_USERPASS_PASSWORD\").unwrap_or_default());\n\n    let (client, login) = client.login_userpass(\"alice\", password).await?;\n    let health = client.sys().health().await?;\n\n    let _token_accessor = login.accessor;\n    println!(\"openbao version: {}\", health.version);\n    Ok(())\n}\n```\n\nAuthenticate with JWT:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let client = Client::new(\"https://bao.example.com:8200\")?;\n    let jwt = SecretString::from(std::env::var(\"SERVICE_JWT\").unwrap_or_default());\n\n    let (client, login) = client.login_jwt(Some(\"web\"), jwt).await?;\n    let health = client.sys().health().await?;\n\n    let _token_accessor = login.accessor;\n    println!(\"openbao version: {}\", health.version);\n    Ok(())\n}\n```\n\nStart an OIDC browser login and handle the callback without logging returned\ntoken material:\n\n```rust,no_run\nuse openbao::auth::jwt::{OidcAuthUrlRequest, OidcCallbackRequest};\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let client = Client::new(\"https://bao.example.com:8200\")?;\n    let jwt = client.jwt()?;\n\n    let auth = jwt\n        .oidc_auth_url(\n            \u0026OidcAuthUrlRequest::new(\"https://app.example.com/oidc/callback\")\n                .with_role(\"web\")\n                .with_client_nonce(\"nonce-from-session\"),\n        )\n        .await?;\n    println!(\"redirect the browser to: {}\", auth.auth_url);\n\n    let code = SecretString::from(std::env::var(\"OIDC_CODE\").unwrap_or_default());\n    let callback = OidcCallbackRequest::with_code(\n        std::env::var(\"OIDC_STATE\").unwrap_or_default(),\n        code,\n    )\n    .with_client_nonce(\"nonce-from-session\");\n\n    let (client, login) = jwt.oidc_callback(\u0026callback).await?;\n    let health = client.sys().health().await?;\n\n    let _token_accessor = login.accessor;\n    println!(\"openbao version: {}\", health.version);\n    Ok(())\n}\n```\n\nWrite and read KV v2 data:\n\n```rust,no_run\nuse openbao::{Client, Result};\nuse openbao::SecretString;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Deserialize, Serialize)]\nstruct DatabaseCredentials {\n    username: String,\n    password: SecretString,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let kv = client.kv2(\"secret\")?;\n\n    kv.write(\n        \"production/database\",\n        DatabaseCredentials {\n            username: \"app\".to_owned(),\n            password: SecretString::from(\"change-me\"),\n        },\n    )\n    .await?;\n\n    let secret = kv\n        .read::\u003cDatabaseCredentials\u003e(\"production/database\")\n        .await?;\n\n    let _username = secret.data.username;\n    let _password = secret.data.password;\n    let names = kv.list(\"production\").await?;\n    let _has_database_entry = names.contains(\"database\");\n    println!(\"database credentials loaded\");\n    Ok(())\n}\n```\n\nUse KV v2 check-and-set, patch, and version operations:\n\n```rust,no_run\nuse openbao::secrets::kv2::{Kv2MetadataOptions, Kv2WriteOptions};\nuse openbao::{Client, Result};\nuse openbao::SecretString;\nuse serde::Serialize;\n\n#[derive(Serialize)]\nstruct Patch {\n    password: SecretString,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let kv = client.kv2(\"secret\")?;\n\n    kv.write_with_options(\n        \"app/config\",\n        serde_json::json!({ \"username\": \"app\", \"password\": \"first\" }),\n        Some(Kv2WriteOptions { cas: Some(0) }),\n    )\n    .await?;\n\n    let second = kv\n        .patch(\"app/config\", Patch {\n            password: SecretString::from(\"rotated\"),\n        })\n        .await?;\n\n    let _previous = kv\n        .read_version::\u003cserde_json::Value\u003e(\"app/config\", second.version - 1)\n        .await?;\n    kv.delete_versions(\"app/config\", \u0026[second.version - 1]).await?;\n    kv.undelete_versions(\"app/config\", \u0026[second.version - 1]).await?;\n    kv.destroy_versions(\"app/config\", \u0026[second.version - 1]).await?;\n\n    kv.patch_metadata(\n        \"app/config\",\n        \u0026Kv2MetadataOptions {\n            max_versions: Some(10),\n            cas_required: Some(true),\n            delete_version_after: Some(\"24h\".to_owned()),\n            custom_metadata: None,\n        },\n    )\n    .await?;\n\n    Ok(())\n}\n```\n\nParse OpenBao timestamps when the `time` feature is enabled:\n\n```rust,no_run\nuse openbao::{\n    Client, Result, SecretString, parse_optional_rfc3339_timestamp,\n    parse_rfc3339_timestamp,\n};\nuse serde::Deserialize;\n\n#[derive(Deserialize)]\nstruct AppConfig {\n    enabled: bool,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let secret = client.kv2(\"secret\")?.read::\u003cAppConfig\u003e(\"services/api\").await?;\n\n    let created_at = parse_rfc3339_timestamp(\u0026secret.metadata.created_time)?;\n    let deleted_at =\n        parse_optional_rfc3339_timestamp(Some(secret.metadata.deletion_time.as_str()))?;\n\n    let _enabled = secret.data.enabled;\n    let _audit_times = (created_at, deleted_at);\n    Ok(())\n}\n```\n\nLoad service configuration from KV v2:\n\n```rust,no_run\nuse openbao::{Client, Result};\nuse openbao::SecretString;\nuse serde::Deserialize;\n\n#[derive(Deserialize)]\nstruct AppConfig {\n    database_url: SecretString,\n    listen_addr: String,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let kv = client.kv2(\"secret\")?;\n\n    let typed = kv.read_data::\u003cAppConfig\u003e(\"services/api\").await?;\n    let env_map = kv.read_service_config(\"services/api-env\").await?;\n    kv.write_service_config(\"services/api-env-copy\", \u0026env_map).await?;\n\n    println!(\"listen: {}\", typed.listen_addr);\n    println!(\"loaded {} secret config keys\", env_map.len());\n    let _database_url_is_not_logged = typed.database_url;\n    Ok(())\n}\n```\n\nShare an authenticated client across async tasks:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString, SharedClient};\n\nfn worker_client(token: SecretString) -\u003e Result\u003cSharedClient\u003e {\n    Ok(Client::new(\"https://bao.example.com:8200\")?\n        .try_with_token(token)?\n        .into_shared())\n}\n```\n\nRead dynamic database credentials:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let database = client.database(\"database\")?;\n\n    let credentials = database.credentials(\"readonly\").await?;\n    let _password = credentials.password;\n    println!(\n        \"database user {} leased for {} seconds\",\n        credentials.username, credentials.lease_duration\n    );\n    Ok(())\n}\n```\n\nCreate and validate a TOTP code without logging the generated code:\n\n```rust,no_run\nuse openbao::secrets::totp::{TotpKeyCreateRequest, TotpValidateRequest};\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let totp = client.totp(\"totp\")?;\n\n    let created = totp\n        .create_key(\"alice\", \u0026TotpKeyCreateRequest::generated(\"Example\", \"alice\"))\n        .await?;\n    println!(\"TOTP bootstrap URL available: {}\", created.url.is_some());\n\n    let code = totp.generate_code(\"alice\").await?;\n    let validation = totp\n        .validate_code(\"alice\", \u0026TotpValidateRequest::new(code.code))\n        .await?;\n\n    println!(\"TOTP code accepted: {}\", validation.valid);\n    Ok(())\n}\n```\n\nIssue an SSH certificate and generated private key without logging the key:\n\n```rust,no_run\nuse openbao::secrets::ssh::{SshIssueKeyType, SshIssueRequest};\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let ssh = client.ssh(\"ssh\")?;\n\n    let issued = ssh\n        .issue(\"user-cert\", \u0026SshIssueRequest::new(SshIssueKeyType::Ed25519))\n        .await?;\n\n    println!(\"SSH certificate length: {}\", issued.signed_key.len());\n    let _private_key_is_not_logged = issued.private_key;\n    Ok(())\n}\n```\n\nProvision ACME external account binding and hand it to an ACME client:\n\n```rust,no_run\nuse openbao::{Client, ExposeSecret, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let pki = client.pki(\"pki\")?;\n\n    let eab = pki.generate_role_acme_eab(\"tls-server\").await?;\n    let directory_url = pki.role_acme_directory_url(\"tls-server\")?;\n\n    let _eab_hmac_key_for_acme_client = eab.key.expose_secret();\n\n    // Pass `directory_url`, `eab.id`, and the exposed EAB HMAC key to a\n    // dedicated ACME client such as instant-acme or acme2. That client owns\n    // account registration, nonce handling, challenge responses, polling, and\n    // certificate download. Treat `eab.key` as credential material.\n    println!(\"ACME directory: {directory_url}\");\n    println!(\"EAB key id: {}\", eab.id);\n\n    Ok(())\n}\n```\n\nUse a KV v1 mount:\n\n```rust,no_run\nuse openbao::{Client, Result};\nuse openbao::SecretString;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Deserialize, Serialize)]\nstruct Config {\n    endpoint: String,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let kv = client.kv1(\"legacy-secret\")?;\n\n    kv.write(\"app/config\", Config {\n        endpoint: \"https://api.example.com\".to_owned(),\n    })\n    .await?;\n\n    let config = kv.read::\u003cConfig\u003e(\"app/config\").await?;\n    let keys = kv.list(\"app\").await?;\n\n    println!(\"endpoint: {}\", config.endpoint);\n    println!(\"keys: {}\", keys.keys.len());\n    Ok(())\n}\n```\n\nEncrypt and decrypt through Transit:\n\n```rust,no_run\nuse openbao::secrets::transit::{TransitDecryptRequest, TransitEncryptRequest};\nuse openbao::{Client, Result};\nuse openbao::{ExposeSecret, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let transit = client.transit(\"transit\")?;\n\n    let encrypted = transit\n        .encrypt(\n            \"app-key\",\n            \u0026TransitEncryptRequest::new(SecretString::from(\"c2VjcmV0\")),\n        )\n        .await?;\n\n    let decrypted = transit\n        .decrypt(\n            \"app-key\",\n            \u0026TransitDecryptRequest::new(encrypted.ciphertext),\n        )\n        .await?;\n\n    println!(\"plaintext length: {}\", decrypted.plaintext.expose_secret().len());\n    Ok(())\n}\n```\n\nEncrypt and decrypt raw bytes with the optional `transit-bytes` feature:\n\n```rust,no_run\nuse openbao::secrets::transit::{TransitDecryptRequest, TransitEncryptRequest};\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let transit = client.transit(\"transit\")?;\n\n    let request = TransitEncryptRequest::from_plaintext_bytes(b\"secret\")?;\n    let encrypted = transit.encrypt(\"app-key\", \u0026request).await?;\n    let decrypted = transit\n        .decrypt(\"app-key\", \u0026TransitDecryptRequest::new(encrypted.ciphertext))\n        .await?;\n\n    let plaintext = decrypted.plaintext_bytes()?;\n    println!(\"plaintext length: {}\", plaintext.len());\n    Ok(())\n}\n```\n\nImport externally wrapped Transit key material:\n\n```rust,no_run\nuse openbao::secrets::transit::{\n    TransitImportHashFunction, TransitImportRequest, TransitKeyType,\n};\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let transit = client.transit(\"transit\")?;\n\n    let wrapping_key = transit.wrapping_key().await?;\n    println!(\"wrapping key PEM length: {}\", wrapping_key.public_key.len());\n\n    let request = TransitImportRequest::new(\n        SecretString::from(std::env::var(\"BAO_WRAPPED_KEY\").unwrap_or_default()),\n        TransitKeyType::Aes256Gcm96,\n    )?\n    .with_hash_function(TransitImportHashFunction::Sha256);\n\n    transit.import_key(\"imported-app-key\", \u0026request).await?;\n    Ok(())\n}\n```\n\nPrepare a wrapped import blob with the optional `transit-import` and\n`transit-import-acknowledged` features:\n\nThis helper is a software convenience path. OpenSSL may allocate intermediate\nkey buffers outside Rust's `zeroize` control, so high-assurance deployments\nshould wrap BYOK material inside an HSM or equivalent audited boundary and pass\nonly the already-wrapped ciphertext to the default import request types.\n\n```rust,ignore\nuse openbao::secrets::transit::{\n    TransitImportHashFunction, TransitKeyType, TransitWrappedImportKey,\n};\nuse openbao::{Client, Result, SecretString, Zeroizing};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let transit = client.transit(\"transit\")?;\n\n    let wrapping_key = transit.wrapping_key().await?;\n    let wrapped = TransitWrappedImportKey::wrap_key_material(\n        \u0026wrapping_key.public_key,\n        Zeroizing::new(b\"32-byte-import-key-material-here\".to_vec()),\n        TransitImportHashFunction::Sha256,\n    )?;\n\n    let request = wrapped.into_import_request(TransitKeyType::Aes256Gcm96)?;\n    transit.import_key(\"imported-app-key\", \u0026request).await?;\n    Ok(())\n}\n```\n\nManage a Transit key and encrypt a small batch:\n\n```rust,no_run\nuse openbao::secrets::transit::{\n    TransitBatchEncryptRequest, TransitCreateKeyRequest, TransitEncryptRequest,\n    TransitTrimRequest, TransitUpdateKeyRequest,\n};\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let transit = client.transit(\"transit\")?;\n\n    transit\n        .create_key(\"app-key\", \u0026TransitCreateKeyRequest::default())\n        .await?;\n    transit.rotate_key(\"app-key\").await?;\n    transit\n        .update_key(\"app-key\", \u0026TransitUpdateKeyRequest {\n            min_decryption_version: Some(1),\n            ..Default::default()\n        })\n        .await?;\n\n    let encrypted = transit\n        .batch_encrypt(\n            \"app-key\",\n            \u0026TransitBatchEncryptRequest {\n                batch_input: vec![\n                    TransitEncryptRequest::new(SecretString::from(\"Zmlyc3Q=\")),\n                    TransitEncryptRequest::new(SecretString::from(\"c2Vjb25k\")),\n                ],\n            },\n        )\n        .await?;\n\n    transit\n        .trim_key(\"app-key\", \u0026TransitTrimRequest::new(1)?)\n        .await?;\n\n    println!(\"encrypted items: {}\", encrypted.batch_results.len());\n    Ok(())\n}\n```\n\nSign data for JWS/JWT-style ECDSA workflows:\n\n```rust,no_run\nuse openbao::secrets::transit::{\n    TransitHashAlgorithm, TransitSignRequest, TransitVerifyRequest,\n};\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n    let transit = client.transit(\"transit\")?;\n\n    let input = SecretString::from(\"ZXhhbXBsZS1wYXlsb2Fk\");\n    let signed = transit\n        .sign(\n            \"jwt-signing-key\",\n            Some(TransitHashAlgorithm::Sha2_256),\n            \u0026TransitSignRequest::jws(input.clone()),\n        )\n        .await?;\n\n    let verified = transit\n        .verify(\n            \"jwt-signing-key\",\n            Some(TransitHashAlgorithm::Sha2_256),\n            \u0026TransitVerifyRequest::jws_with_signature(input, signed.signature),\n        )\n        .await?;\n\n    println!(\"signature valid: {}\", verified.valid);\n    Ok(())\n}\n```\n\nCreate a constrained token role for repeatable service-token issuance:\n\n```rust,no_run\nuse openbao::auth::token::TokenRole;\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let role = TokenRole::default()\n        .with_allowed_policies([\"app-read\", \"app-transit\"])\n        .with_token_ttl(\"30m\")?;\n\n    client.token().write_role(\"app-service\", \u0026role).await?;\n    let roles = client.token().list_roles().await?;\n\n    println!(\"configured token roles: {}\", roles.keys.len());\n    Ok(())\n}\n```\n\nCreate, inspect, renew, and revoke child or orphan tokens:\n\n```rust,no_run\nuse openbao::auth::token::TokenCreateRequest;\nuse openbao::{Client, Result};\nuse openbao::SecretString;\nuse std::collections::BTreeMap;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let root_or_parent = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(root_or_parent)?;\n\n    let child = client\n        .token()\n        .create(\n            \u0026TokenCreateRequest {\n                meta: BTreeMap::from([(\"owner\".to_owned(), \"example\".to_owned())]),\n                display_name: Some(\"example-child\".to_owned()),\n                renewable: Some(true),\n                ..TokenCreateRequest::default()\n            }\n            .with_policies([\"default\"])\n            .with_ttl(\"30m\")?\n            .with_explicit_max_ttl(\"1h\")?,\n        )\n        .await?;\n\n    let info = client.token().lookup(\u0026child.client_token).await?;\n    println!(\"renewable: {}\", info.renewable);\n\n    let _renewed = client.token().renew(\u0026child.client_token, Some(\"15m\")).await?;\n    client.token().revoke_accessor(\u0026child.accessor).await?;\n\n    let orphan = client\n        .token()\n        .create_orphan(\n            \u0026TokenCreateRequest {\n                display_name: Some(\"example-orphan\".to_owned()),\n                renewable: Some(true),\n                ..TokenCreateRequest::default()\n            }\n            .with_policies([\"app-read\"])\n            .with_ttl(\"30m\")?,\n        )\n        .await?;\n\n    let _renewed_by_accessor = client\n        .token()\n        .renew_accessor(\u0026orphan.accessor, Some(\"15m\"))\n        .await?;\n    client.token().revoke_accessor(\u0026orphan.accessor).await?;\n    Ok(())\n}\n```\n\nEnable a KV v2 mount:\n\n```rust,no_run\nuse openbao::{Client, Result};\nuse openbao::SecretString;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    client\n        .sys()\n        .enable_kv2(\"apps\", Some(\"application secrets\"))\n        .await?;\n\n    let mounts = client.sys().list_mounts().await?;\n    println!(\"mount count: {}\", mounts.len());\n    Ok(())\n}\n```\n\nWait for OpenBao readiness with a runtime-provided sleep function:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\nuse std::time::Duration;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    client\n        .sys()\n        .wait_ready_with_delay(\n            Duration::from_secs(30),\n            Duration::from_millis(250),\n            tokio::time::sleep,\n        )\n        .await?;\n\n    println!(\"OpenBao is ready for authenticated requests\");\n    Ok(())\n}\n```\n\nWait until an initialized OpenBao node is unsealed:\n\n```rust,no_run\nuse openbao::{Client, Result};\nuse std::time::Duration;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let client = Client::new(\"https://bao.example.com:8200\")?;\n\n    let status = client\n        .sys()\n        .wait_until_unsealed_with_delay(\n            Duration::from_secs(60),\n            Duration::from_millis(250),\n            tokio::time::sleep,\n        )\n        .await?;\n\n    println!(\"OpenBao {} is unsealed\", status.version);\n    Ok(())\n}\n```\n\nRequest a typed response-wrapped JSON result:\n\n```rust,no_run\nuse openbao::{Client, Method, ResponseEnvelope, Result, SecretString};\nuse serde::Deserialize;\n\n#[derive(Deserialize)]\nstruct DbCredential {\n    username: String,\n    password: SecretString,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let wrapped = client\n        .wrapping(\"5m\")?\n        .request_json::\u003cResponseEnvelope\u003cDbCredential\u003e, openbao::Empty\u003e(\n            Method::GET,\n            \"database/creds/reporting\",\n            None,\n        )\n        .await?;\n\n    // Deliver wrapped.token() to the intended recipient. The recipient can\n    // unwrap once; Debug output redacts the token and accessor.\n    let response = wrapped.unwrap().await?;\n    println!(\"issued credential for {}\", response.data.username);\n    Ok(())\n}\n```\n\nCount leases and revoke an application lease prefix:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let counts = client.sys().count_leases(None).await?;\n    client\n        .sys()\n        .revoke_lease_prefix(\"database/creds/old-service\", Some(true))\n        .await?;\n\n    println!(\"lease count buckets: {}\", counts.counts.len());\n    Ok(())\n}\n```\n\nWrap and unwrap JSON data:\n\n```rust,no_run\nuse openbao::{Client, Result};\nuse openbao::SecretString;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Deserialize, Serialize)]\nstruct WrappedPayload {\n    nonce: String,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let wrap = client\n        .sys()\n        .wrapping_wrap(\"5m\", \u0026WrappedPayload {\n            nonce: \"one-time\".to_owned(),\n        })\n        .await?;\n\n    let payload = client\n        .sys()\n        .wrapping_unwrap::\u003cWrappedPayload\u003e(Some(\u0026wrap.token))\n        .await?;\n\n    println!(\"payload nonce length: {}\", payload.nonce.len());\n    Ok(())\n}\n```\n\nWrite an ACL policy and check capabilities:\n\n```rust,no_run\nuse openbao::{AclPolicyBuilder, Client, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let mut policy = AclPolicyBuilder::new();\n    let request = policy\n        .allow_kv2_read_prefix(\"secret\", \"app\")?\n        .build_write_request()?;\n\n    client\n        .sys()\n        .write_policy(\"app-read\", \u0026request)\n        .await?;\n\n    let capabilities = client.sys().capabilities_self([\"secret/data/app\"]).await?;\n    let _for_path = capabilities.by_path.get(\"secret/data/app\");\n    Ok(())\n}\n```\n\nRequire response wrapping in an ACL policy:\n\n```rust,no_run\nuse openbao::{AclCapability, AclPolicyBuilder, Result};\n\nfn build_policy() -\u003e Result\u003cString\u003e {\n    let mut policy = AclPolicyBuilder::new();\n    policy\n        .allow_path_with_wrapping(\n            \"secret/data/app/*\",\n            [AclCapability::Read],\n            Some(\"30s\"),\n            Some(\"5m\"),\n        )?\n        .allow_kv2_read_prefix_with_required_wrapping(\"secret\", \"ops\", \"1m\")?;\n\n    policy.build()\n}\n```\n\nRun a small idempotent service bootstrap:\n\n```rust,no_run\nuse openbao::auth::token::TokenCreateRequest;\nuse openbao::bootstrap::AdminBootstrap;\nuse openbao::secrets::transit::TransitCreateKeyRequest;\nuse openbao::{AclPolicyBuilder, Client, Result, SecretString};\nuse std::collections::BTreeMap;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let mut policy = AclPolicyBuilder::new();\n    policy.allow_kv2_read_prefix(\"secret\", \"app\")?;\n\n    let mut values = BTreeMap::new();\n    values.insert(\"API_KEY\".to_owned(), SecretString::from(std::env::var(\"APP_API_KEY\").unwrap_or_default()));\n\n    let mut bootstrap = AdminBootstrap::new();\n    bootstrap\n        .ensure_kv2_mount(\"secret\", Some(\"application secrets\"))?\n        .ensure_transit_mount(\"transit\", Some(\"application crypto\"))?\n        .ensure_pki_mount(\"pki\", Some(\"application certificate roles\"))?\n        .ensure_database_mount(\"database\", Some(\"application database roles\"))?\n        .ensure_ssh_mount(\"ssh\", Some(\"application SSH roles\"))?\n        .ensure_policy(\"app-read\", \u0026policy)?\n        .ensure_transit_key(\"transit\", \"app-key\", TransitCreateKeyRequest::default())?\n        .ensure_kv2_secret_values(\"secret\", \"app/config\", values)?\n        .issue_service_token(\n            \"app\",\n            TokenCreateRequest::default()\n                .with_policies([\"app-read\"])\n                .without_default_policy()\n                .with_ttl(\"1h\")?,\n        )?;\n\n    let preview = bootstrap.preview(\u0026client).await?;\n    for step in preview.changed_steps() {\n        println!(\"would change {} {}\", step.target_type, step.target);\n    }\n\n    let report = bootstrap.run(\u0026client).await?;\n\n    println!(\"bootstrap steps: {}\", report.steps.len());\n    let _issued_token_is_not_logged = report.issued_tokens;\n    Ok(())\n}\n```\n\nBuild an advisory FIPS-oriented posture report:\n\n```rust,no_run\nuse openbao::posture::FipsPosture;\nuse openbao::secrets::transit::{TransitCreateKeyRequest, TransitHashAlgorithm, TransitKeyType};\n\nfn main() {\n    let mut posture = FipsPosture::new();\n    posture\n        .check_transit_create_key(\n            \"transit/app-key\",\n            \u0026TransitCreateKeyRequest {\n                key_type: Some(TransitKeyType::Aes256Gcm96),\n                exportable: Some(false),\n                allow_plaintext_backup: Some(false),\n                ..Default::default()\n            },\n        )\n        .check_transit_hash_algorithm(\"transit/hash\", TransitHashAlgorithm::Sha2_256)\n        .assume_unknown_or_non_hsm_seal(\"seal\");\n\n    let report = posture.finish();\n    for finding in \u0026report.findings {\n        println!(\"{:?}: {} - {}\", finding.severity, finding.subject, finding.message);\n    }\n}\n```\n\nThe posture report only covers choices visible to the SDK. It does not certify\nOpenBao, your cryptographic provider, HSM/KMS setup, TLS stack, operating\nsystem, or deployment process.\n\nEnable an audit device and calculate an audit hash:\n\n```rust,no_run\nuse openbao::{Client, Result, SecretString};\nuse openbao::sys::AuditEnableRequest;\nuse std::collections::BTreeMap;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    client\n        .sys()\n        .enable_audit_device(\n            \"file\",\n            \u0026AuditEnableRequest {\n                options: BTreeMap::from([(\n                    \"file_path\".to_owned(),\n                    \"/var/log/openbao/audit.log\".to_owned(),\n                )]),\n                ..AuditEnableRequest::new(\"file\").with_description(\"local audit file\")\n            },\n        )\n        .await?;\n\n    let hash = client\n        .sys()\n        .audit_hash(\"file\", \u0026SecretString::from(\"known-secret-value\"))\n        .await?;\n\n    println!(\"audit hash prefix: {}\", hash.hash.split(':').next().unwrap_or(\"\"));\n    Ok(())\n}\n```\n\nLook up and renew one exact lease:\n\n```rust,no_run\nuse openbao::{Client, RenewalHint, Result, SecretString};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let lease_id = SecretString::from(std::env::var(\"BAO_LEASE_ID\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let lease = client.sys().lookup_lease(\u0026lease_id).await?;\n    let hint = RenewalHint::from_ttl(lease.ttl, lease.renewable);\n\n    if let (Some(sleep_for), Some(increment)) =\n        (hint.sleep_before_renew, hint.increment_seconds)\n    {\n        println!(\"renew after roughly {} seconds\", sleep_for.as_secs());\n        let renewed = client.sys().renew_lease(\u0026lease_id, Some(increment)).await?;\n        println!(\"renewed lease seconds: {}\", renewed.lease_duration);\n    }\n\n    Ok(())\n}\n```\n\nRead and reload a plugin catalog entry:\n\n```rust,no_run\nuse openbao::sys::{PluginReloadRequest, PluginType};\nuse openbao::{Client, Result};\nuse openbao::SecretString;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let plugins = client.sys().list_plugins_by_type(PluginType::Secret).await?;\n    if plugins.keys.iter().any(|name| name == \"transit\") {\n        let _info = client\n            .sys()\n            .read_plugin(PluginType::Secret, \"transit\", None)\n            .await?;\n    }\n\n    client\n        .sys()\n        .reload_plugin_backend(\u0026PluginReloadRequest {\n            plugin: Some(\"example-plugin\".to_owned()),\n            mounts: Vec::new(),\n            scope: None,\n        })\n        .await?;\n\n    Ok(())\n}\n```\n\nDiscover OpenBao's OpenAPI document:\n\n```rust,no_run\nuse openbao::{Client, Result};\nuse openbao::SecretString;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let token = SecretString::from(std::env::var(\"BAO_TOKEN\").unwrap_or_default());\n    let client = Client::new(\"https://bao.example.com:8200\")?.try_with_token(token)?;\n\n    let response = client.sys().openapi_document(true).await?;\n\n    println!(\"openapi keys: {}\", response.as_object().map_or(0, |object| object.len()));\n    Ok(())\n}\n```\n\nThe raw request layer is intentionally low level. Prefer typed helpers when the\ncrate supports an endpoint; use raw JSON to bridge missing OpenBao APIs while\ncoverage grows.\n\nFor application-specific OpenBao plugins, keep raw JSON calls behind a small\ntyped wrapper built with `PluginMount`, the public path validators, and bounded\nlist helpers such as `BoundedStringList`. See\n[Typed Custom Plugin Pattern](docs/CUSTOM_PLUGIN_PATTERN.md) for a complete\nrequest/response wrapper example with path validation, secret redaction, and\ntest guidance. Generic plugin traits are intentionally out of scope because\nplugin schemas are deployment-specific.\n\n## Local OpenBao Dev Instance\n\nThe local dev stack uses Podman, TLS, a private CA, and loopback-only ports in\nthe requested `994x` range.\n\nPrepare the rootless Podman volume and TLS assets without starting OpenBao:\n\n```bash\nscripts/openbao_dev.sh prepare\n```\n\nThis project does not require a `/srv` directory tree for local development:\nraft data lives in a Podman-managed volume, and TLS material lives in the\nignored `deploy/podman/dev-state/` directory.\n\n```bash\nscripts/openbao_dev.sh up\n```\n\nEndpoints:\n\n- API: `https://127.0.0.1:9940`\n- Cluster: `https://127.0.0.1:9941`\n- CA certificate: `deploy/podman/dev-state/tls/dev-ca.crt`\n\nInitialize and unseal OpenBao using `bao operator init` and\n`bao operator unseal`, then export `BAO_ADDR=https://127.0.0.1:9940` and\n`BAO_CACERT=deploy/podman/dev-state/tls/dev-ca.crt`.\n\nFor disposable local development, the crate can initialize and unseal a fresh\nnumeric-loopback instance directly:\n\n```rust,no_run\nuse openbao::{Client, OpenBaoConfig, Result, sys::DevBootstrapOptions};\nuse openbao::Certificate;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let ca_pem = std::fs::read(\"deploy/podman/dev-state/tls/dev-ca.crt\").map_err(|_| {\n        openbao::Error::InvalidTlsConfig(\n            \"failed to read the configured CA certificate file\".into(),\n        )\n    })?;\n    let ca = Certificate::from_pem(\u0026ca_pem)?;\n\n    let config = OpenBaoConfig::new(\"https://127.0.0.1:9940\")?\n        .only_root_certificates(vec![ca])?;\n    let client = Client::from_config(config)?;\n\n    let bootstrap = client\n        .sys()\n        .bootstrap_dev(\u0026DevBootstrapOptions::single_key())\n        .await?;\n\n    let health = bootstrap.client.sys().health().await?;\n    println!(\"initialized: {}, sealed: {}\", health.initialized, health.sealed);\n    Ok(())\n}\n```\n\n`bootstrap_dev` is not a production initialization ceremony. It creates root\nand unseal material in process memory, uses Shamir keys, refuses non-loopback\ntargets, and refuses already initialized servers. Do not use it with HSM/KMS\nauto-unseal, shared environments, or any instance that requires operator key\nceremony.\n\nRun the real OpenBao integration flow:\n\n```bash\nscripts/openbao_integration.sh\n```\n\nThe integration script creates a fresh TLS dev instance, initializes and\nunseals it, stores the root token in a temporary `0600` file for the test\nprocess, and removes that file when the run exits.\n\n## Release Discipline\n\nRun the normal local checks:\n\n```bash\nscripts/checks.sh\n```\n\nRun the current release gate:\n\n```bash\nscripts/release_0_9_gate.sh\n```\n\nSet `OPENBAO_SKIP_INTEGRATION=1` only when Podman is unavailable; release\ncandidate validation should run the integration gate.\n\nNo release tag should be cut unless the matching pentest report status is\nreviewed and recorded in the release notes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvalkyoth%2Fopenbao-rust-crate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvalkyoth%2Fopenbao-rust-crate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvalkyoth%2Fopenbao-rust-crate/lists"}