From cc2c3f7a3be7235e07fac71e16a04ef6fd9f1a5b Mon Sep 17 00:00:00 2001 From: Sienna Meridian Satterwhite Date: Sun, 5 Apr 2026 22:34:41 +0100 Subject: [PATCH] refactor(openbao): migrate to vaultrs client library Replace hand-rolled OpenBao HTTP client with vaultrs 0.8.0, which has official OpenBao support. BaoClient remains the public API so callers are unchanged. KV patch uses raw HTTP since vaultrs doesn't expose it yet. --- Cargo.lock | 430 +++++++++++++++++--- Cargo.toml | 3 + src/openbao.rs | 487 ++++++++--------------- src/workflows/seed/steps/openbao_init.rs | 4 +- 4 files changed, 547 insertions(+), 377 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7cc1cec..9bc974a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,7 +117,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -128,7 +128,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -188,8 +188,8 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.117", + "synstructure 0.13.2", ] [[package]] @@ -200,7 +200,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -244,7 +244,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -255,7 +255,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -426,6 +426,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -514,7 +520,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -538,6 +544,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -722,7 +738,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -746,7 +762,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -757,7 +773,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -792,7 +808,7 @@ checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -829,6 +845,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + [[package]] name = "des" version = "0.8.1" @@ -879,7 +926,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -948,7 +995,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1029,7 +1076,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1045,7 +1092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1246,7 +1293,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1848,6 +1895,50 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -1996,7 +2087,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn", + "syn 2.0.117", ] [[package]] @@ -2228,7 +2319,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2531,7 +2622,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2561,7 +2652,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2686,7 +2777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -2743,6 +2834,7 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -2970,6 +3062,41 @@ dependencies = [ "webpki-roots 1.0.6", ] +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -3177,6 +3304,40 @@ dependencies = [ "nom 7.1.3", ] +[[package]] +name = "rustify" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4800ce4c1cc2fec12c559dae2ddbf0e17fcee7569b796e6d75898efef443368b" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "http", + "reqwest 0.13.2", + "rustify_derive", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 1.0.69", + "tracing", + "url", +] + +[[package]] +name = "rustify_derive" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ea7fda74240f7410d0198b603a8a2f662acc7d76b6667a49f9b162cd8d9b4f" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "serde_urlencoded", + "syn 1.0.109", + "synstructure 0.12.6", +] + [[package]] name = "rustix" version = "1.1.4" @@ -3187,7 +3348,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3250,6 +3411,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs 0.8.3", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.7.0", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.10" @@ -3283,6 +3471,15 @@ dependencies = [ "cipher", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.29" @@ -3313,7 +3510,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.117", ] [[package]] @@ -3441,7 +3638,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3452,7 +3649,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3578,7 +3775,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3661,7 +3858,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.117", ] [[package]] @@ -3684,7 +3881,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.117", "tokio", "url", ] @@ -3914,7 +4111,7 @@ dependencies = [ "pkcs8", "rand 0.8.5", "rcgen", - "reqwest", + "reqwest 0.12.28", "rsa", "russh", "russh-keys", @@ -3930,6 +4127,7 @@ dependencies = [ "tokio-stream", "tracing", "tracing-subscriber", + "vaultrs", "wfe", "wfe-core", "wfe-sqlite", @@ -3959,7 +4157,7 @@ dependencies = [ "pkcs8", "rand 0.8.5", "rcgen", - "reqwest", + "reqwest 0.12.28", "rsa", "russh", "russh-keys", @@ -3976,6 +4174,17 @@ dependencies = [ "wiremock", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -3996,6 +4205,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -4004,7 +4225,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4028,7 +4249,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4057,7 +4278,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4068,7 +4289,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4170,7 +4391,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4291,7 +4512,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4477,6 +4698,25 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vaultrs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ffcc0e81025065dda612ec1e26a3d81bb16ef3062354873d17a35965d68522" +dependencies = [ + "async-trait", + "derive_builder", + "http", + "reqwest 0.13.2", + "rustify", + "rustify_derive", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", + "url", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -4489,6 +4729,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4574,7 +4824,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -4641,6 +4891,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -4758,6 +5017,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -4795,7 +5063,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4806,7 +5074,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4834,6 +5102,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4879,6 +5156,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4927,6 +5219,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4945,6 +5243,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4963,6 +5267,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4993,6 +5303,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5011,6 +5327,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5029,6 +5351,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5047,6 +5375,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5118,7 +5452,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5134,7 +5468,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5262,8 +5596,8 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.117", + "synstructure 0.13.2", ] [[package]] @@ -5283,7 +5617,7 @@ checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5303,8 +5637,8 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.117", + "synstructure 0.13.2", ] [[package]] @@ -5343,7 +5677,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9f452528..f61d1167 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,9 @@ tempfile = "3" dirs = "5" chrono = { version = "0.4", features = ["serde"] } +# OpenBao/Vault client +vaultrs = "0.8" + # Workflow engine wfe = { version = "1.6.3", registry = "sunbeam" } wfe-core = { version = "1.6.3", registry = "sunbeam", features = ["test-support"] } diff --git a/src/openbao.rs b/src/openbao.rs index 630880bc..9e945b11 100644 --- a/src/openbao.rs +++ b/src/openbao.rs @@ -1,233 +1,159 @@ -//! Lightweight OpenBao/Vault HTTP API client. +//! OpenBao/Vault client — thin wrapper around vaultrs. //! -//! Replaces all `kubectl exec openbao-0 -- sh -c "bao ..."` calls from the -//! Python version with direct HTTP API calls via port-forward to openbao:8200. +//! Provides a `BaoClient` API that can be swapped to a different backend +//! without changing callers. use crate::error::{Result, ResultExt}; -use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use vaultrs::client::{Client, VaultClient, VaultClientSettingsBuilder}; -/// OpenBao HTTP client wrapping a base URL and optional root token. -#[derive(Clone)] +/// OpenBao HTTP client wrapping vaultrs::VaultClient. pub struct BaoClient { + inner: VaultClient, pub base_url: String, - pub token: Option, - http: reqwest::Client, } -// ── API response types ────────────────────────────────────────────────────── +// Re-export the init response type for callers that need it. +pub use vaultrs::api::sys::responses::StartInitializationResponse as InitResponse; -#[derive(Debug, Deserialize)] -pub struct InitResponse { - #[serde(alias = "unseal_keys_b64")] - pub keys_base64: Vec, - pub root_token: String, -} - -#[derive(Debug, Deserialize)] +/// Seal status response. +#[derive(Debug, Default)] pub struct SealStatusResponse { - #[serde(default)] pub initialized: bool, - #[serde(default)] pub sealed: bool, - #[serde(default)] - pub progress: u32, - #[serde(default)] - pub t: u32, - #[serde(default)] - pub n: u32, } -#[derive(Debug, Deserialize)] +/// Unseal response. +#[derive(Debug, Default)] pub struct UnsealResponse { - #[serde(default)] pub sealed: bool, - #[serde(default)] - pub progress: u32, } -/// KV v2 read response wrapper. -#[derive(Debug, Deserialize)] -struct KvReadResponse { - data: Option, -} - -#[derive(Debug, Deserialize)] -struct KvReadData { - data: Option>, -} - -// ── Client implementation ─────────────────────────────────────────────────── - impl BaoClient { /// Create a new client pointing at `base_url` (e.g. `http://localhost:8200`). pub fn new(base_url: &str) -> Self { + let url = base_url.trim_end_matches('/'); + let settings = VaultClientSettingsBuilder::default() + .address(url) + .build() + .expect("valid vault client settings"); Self { - base_url: base_url.trim_end_matches('/').to_string(), - token: None, - http: reqwest::Client::new(), + inner: VaultClient::new(settings).expect("valid vault client"), + base_url: url.to_string(), } } /// Create a client with an authentication token. pub fn with_token(base_url: &str, token: &str) -> Self { - let mut client = Self::new(base_url); - client.token = Some(token.to_string()); - client - } - - fn url(&self, path: &str) -> String { - format!("{}/v1/{}", self.base_url, path.trim_start_matches('/')) - } - - fn request(&self, method: reqwest::Method, path: &str) -> reqwest::RequestBuilder { - let mut req = self.http.request(method, self.url(path)); - if let Some(ref token) = self.token { - req = req.header("X-Vault-Token", token); + let url = base_url.trim_end_matches('/'); + let settings = VaultClientSettingsBuilder::default() + .address(url) + .token(token.to_string()) + .build() + .expect("valid vault client settings"); + Self { + inner: VaultClient::new(settings).expect("valid vault client"), + base_url: url.to_string(), } - req + } + + fn token_header(&self) -> Option { + let t = &self.inner.settings().token; + if t.is_empty() { None } else { Some(t.clone()) } } // ── System operations ─────────────────────────────────────────────── - /// Get the seal status of the OpenBao instance. pub async fn seal_status(&self) -> Result { - let resp = self - .http - .get(format!("{}/v1/sys/seal-status", self.base_url)) - .send() - .await - .ctx("Failed to connect to OpenBao")?; - if !resp.status().is_success() { - let status = resp.status(); - let body = resp.text().await.unwrap_or_default(); - bail!("OpenBao seal-status returned {status}: {body}"); + match vaultrs::sys::status(&self.inner).await { + Ok(status) => { + use vaultrs::sys::ServerStatus; + let (initialized, sealed) = match status { + ServerStatus::OK => (true, false), + ServerStatus::SEALED => (true, true), + ServerStatus::PERFSTANDBY | ServerStatus::STANDBY => (true, false), + ServerStatus::RECOVERY => (true, true), + ServerStatus::UNINITIALIZED | ServerStatus::UNKNOWN => (false, true), + }; + Ok(SealStatusResponse { initialized, sealed }) + } + Err(e) => Err(crate::error::SunbeamError::Other(format!( + "Failed to get seal status: {e}" + ))), } - resp.json().await.ctx("Failed to parse seal status") } - /// Initialize OpenBao with the given number of key shares and threshold. pub async fn init(&self, key_shares: u32, key_threshold: u32) -> Result { - #[derive(Serialize)] - struct InitRequest { - secret_shares: u32, - secret_threshold: u32, - } - - let resp = self - .http - .put(format!("{}/v1/sys/init", self.base_url)) - .json(&InitRequest { - secret_shares: key_shares, - secret_threshold: key_threshold, - }) - .send() - .await - .ctx("Failed to initialize OpenBao")?; - - if !resp.status().is_success() { - let status = resp.status(); - let body = resp.text().await.unwrap_or_default(); - bail!("OpenBao init returned {status}: {body}"); - } - resp.json().await.ctx("Failed to parse init response") + vaultrs::sys::start_initialization( + &self.inner, + key_shares as u64, + key_threshold as u64, + None, + ) + .await + .map_err(|e| crate::error::SunbeamError::Other(format!("OpenBao init failed: {e}"))) } - /// Unseal OpenBao with one key share. pub async fn unseal(&self, key: &str) -> Result { - #[derive(Serialize)] - struct UnsealRequest<'a> { - key: &'a str, - } - - let resp = self - .http - .put(format!("{}/v1/sys/unseal", self.base_url)) - .json(&UnsealRequest { key }) - .send() - .await - .ctx("Failed to unseal OpenBao")?; - - if !resp.status().is_success() { - let status = resp.status(); - let body = resp.text().await.unwrap_or_default(); - bail!("OpenBao unseal returned {status}: {body}"); - } - resp.json().await.ctx("Failed to parse unseal response") + let resp = vaultrs::sys::unseal( + &self.inner, + Some(key.to_string()), + None, + None, + ) + .await + .map_err(|e| crate::error::SunbeamError::Other(format!("OpenBao unseal failed: {e}")))?; + Ok(UnsealResponse { sealed: resp.sealed }) } // ── Secrets engine management ─────────────────────────────────────── - /// Enable a secrets engine at the given path. - /// Returns Ok(()) even if already enabled (400 is tolerated). pub async fn enable_secrets_engine(&self, path: &str, engine_type: &str) -> Result<()> { - #[derive(Serialize)] - struct EnableRequest<'a> { - r#type: &'a str, - } - - let resp = self - .request(reqwest::Method::POST, &format!("sys/mounts/{path}")) - .json(&EnableRequest { - r#type: engine_type, - }) - .send() - .await - .ctx("Failed to enable secrets engine")?; - - let status = resp.status(); - if status.is_success() || status.as_u16() == 400 { - // 400 = "path is already in use" — idempotent - Ok(()) - } else { - let body = resp.text().await.unwrap_or_default(); - bail!("Enable secrets engine {path} returned {status}: {body}"); + match vaultrs::sys::mount::enable(&self.inner, path, engine_type, None).await { + Ok(()) => Ok(()), + Err(e) => { + let msg = e.to_string(); + if msg.contains("400") || msg.contains("already in use") { + Ok(()) // idempotent + } else { + Err(crate::error::SunbeamError::Other(format!( + "Enable secrets engine {path}: {e}" + ))) + } + } } } // ── KV v2 operations ──────────────────────────────────────────────── - /// Read all fields from a KV v2 secret path. - /// Returns None if the path doesn't exist (404). pub async fn kv_get(&self, mount: &str, path: &str) -> Result>> { - let resp = self - .request(reqwest::Method::GET, &format!("{mount}/data/{path}")) - .send() - .await - .ctx("Failed to read KV secret")?; - - if resp.status().as_u16() == 404 { - return Ok(None); + match vaultrs::kv2::read::>(&self.inner, mount, path).await { + Ok(data) => { + let result: HashMap = data + .into_iter() + .map(|(k, v)| { + let s = match v { + serde_json::Value::String(s) => s, + other => other.to_string(), + }; + (k, s) + }) + .collect(); + Ok(Some(result)) + } + Err(e) => { + let msg = e.to_string(); + if msg.contains("404") || msg.contains("Not Found") { + Ok(None) + } else { + Err(crate::error::SunbeamError::Other(format!( + "KV get {mount}/{path}: {e}" + ))) + } + } } - if !resp.status().is_success() { - let status = resp.status(); - let body = resp.text().await.unwrap_or_default(); - bail!("KV get {mount}/{path} returned {status}: {body}"); - } - - let kv_resp: KvReadResponse = resp.json().await.ctx("Failed to parse KV response")?; - let data = kv_resp - .data - .and_then(|d| d.data) - .unwrap_or_default(); - - // Convert all values to strings - let result: HashMap = data - .into_iter() - .map(|(k, v)| { - let s = match v { - serde_json::Value::String(s) => s, - other => other.to_string(), - }; - (k, s) - }) - .collect(); - - Ok(Some(result)) } - /// Read a single field from a KV v2 secret path. - /// Returns empty string if path or field doesn't exist. pub async fn kv_get_field(&self, mount: &str, path: &str, field: &str) -> Result { match self.kv_get(mount, path).await? { Some(data) => Ok(data.get(field).cloned().unwrap_or_default()), @@ -235,53 +161,32 @@ impl BaoClient { } } - /// Write (create or overwrite) all fields in a KV v2 secret path. - pub async fn kv_put( - &self, - mount: &str, - path: &str, - data: &HashMap, - ) -> Result<()> { - #[derive(Serialize)] - struct KvWriteRequest<'a> { - data: &'a HashMap, - } - - let resp = self - .request(reqwest::Method::POST, &format!("{mount}/data/{path}")) - .json(&KvWriteRequest { data }) - .send() + pub async fn kv_put(&self, mount: &str, path: &str, data: &HashMap) -> Result<()> { + vaultrs::kv2::set(&self.inner, mount, path, data) .await - .ctx("Failed to write KV secret")?; - - if !resp.status().is_success() { - let status = resp.status(); - let body = resp.text().await.unwrap_or_default(); - bail!("KV put {mount}/{path} returned {status}: {body}"); - } + .map_err(|e| crate::error::SunbeamError::Other(format!("KV put {mount}/{path}: {e}")))?; Ok(()) } - /// Patch (merge) fields into an existing KV v2 secret path. - pub async fn kv_patch( - &self, - mount: &str, - path: &str, - data: &HashMap, - ) -> Result<()> { - #[derive(Serialize)] + /// Patch (merge) fields into an existing KV v2 secret. + /// vaultrs doesn't have a patch method, so we use a raw HTTP request. + pub async fn kv_patch(&self, mount: &str, path: &str, data: &HashMap) -> Result<()> { + #[derive(serde::Serialize)] struct KvWriteRequest<'a> { data: &'a HashMap, } - let resp = self - .request(reqwest::Method::PATCH, &format!("{mount}/data/{path}")) + let url = format!("{}/v1/{mount}/data/{path}", self.base_url); + let mut req = reqwest::Client::new() + .patch(&url) .header("Content-Type", "application/merge-patch+json") - .json(&KvWriteRequest { data }) - .send() - .await - .ctx("Failed to patch KV secret")?; + .json(&KvWriteRequest { data }); + if let Some(token) = self.token_header() { + req = req.header("X-Vault-Token", token); + } + + let resp = req.send().await.ctx("Failed to patch KV secret")?; if !resp.status().is_success() { let status = resp.status(); let body = resp.text().await.unwrap_or_default(); @@ -290,89 +195,57 @@ impl BaoClient { Ok(()) } - /// Delete a KV v2 secret path (soft delete — deletes latest version). pub async fn kv_delete(&self, mount: &str, path: &str) -> Result<()> { - let resp = self - .request(reqwest::Method::DELETE, &format!("{mount}/data/{path}")) - .send() - .await - .ctx("Failed to delete KV secret")?; - - // 404 is fine (already deleted) - if !resp.status().is_success() && resp.status().as_u16() != 404 { - let status = resp.status(); - let body = resp.text().await.unwrap_or_default(); - bail!("KV delete {mount}/{path} returned {status}: {body}"); + match vaultrs::kv2::delete_latest(&self.inner, mount, path).await { + Ok(()) => Ok(()), + Err(e) => { + let msg = e.to_string(); + if msg.contains("404") { + Ok(()) + } else { + Err(crate::error::SunbeamError::Other(format!( + "KV delete {mount}/{path}: {e}" + ))) + } + } } - Ok(()) } // ── Auth operations ───────────────────────────────────────────────── - /// Enable an auth method at the given path. - /// Tolerates "already enabled" (400/409). pub async fn auth_enable(&self, path: &str, method_type: &str) -> Result<()> { - #[derive(Serialize)] - struct AuthEnableRequest<'a> { - r#type: &'a str, - } - - let resp = self - .request(reqwest::Method::POST, &format!("sys/auth/{path}")) - .json(&AuthEnableRequest { - r#type: method_type, - }) - .send() - .await - .ctx("Failed to enable auth method")?; - - let status = resp.status(); - if status.is_success() || status.as_u16() == 400 { - Ok(()) - } else { - let body = resp.text().await.unwrap_or_default(); - bail!("Enable auth {path} returned {status}: {body}"); + match vaultrs::sys::auth::enable(&self.inner, path, method_type, None).await { + Ok(()) => Ok(()), + Err(e) => { + let msg = e.to_string(); + if msg.contains("400") || msg.contains("already in use") { + Ok(()) + } else { + Err(crate::error::SunbeamError::Other(format!( + "Enable auth {path}: {e}" + ))) + } + } } } - /// Write a policy. pub async fn write_policy(&self, name: &str, policy_hcl: &str) -> Result<()> { - #[derive(Serialize)] - struct PolicyRequest<'a> { - policy: &'a str, - } - - let resp = self - .request( - reqwest::Method::PUT, - &format!("sys/policies/acl/{name}"), - ) - .json(&PolicyRequest { policy: policy_hcl }) - .send() + vaultrs::sys::policy::set(&self.inner, name, policy_hcl) .await - .ctx("Failed to write policy")?; - - if !resp.status().is_success() { - let status = resp.status(); - let body = resp.text().await.unwrap_or_default(); - bail!("Write policy {name} returned {status}: {body}"); - } - Ok(()) + .map_err(|e| crate::error::SunbeamError::Other(format!("Write policy {name}: {e}"))) } - /// Write to an arbitrary API path (for auth config, roles, database config, etc.). - pub async fn write( - &self, - path: &str, - data: &serde_json::Value, - ) -> Result { - let resp = self - .request(reqwest::Method::POST, path) - .json(data) - .send() - .await - .with_ctx(|| format!("Failed to write to {path}"))?; + // ── Generic write (for auth config, roles, etc.) ──────────────────── + pub async fn write(&self, path: &str, data: &serde_json::Value) -> Result { + let url = format!("{}/v1/{}", self.base_url, path.trim_start_matches('/')); + let mut req = reqwest::Client::new().post(&url).json(data); + if let Some(token) = self.token_header() { + req = req.header("X-Vault-Token", token); + } + + let resp = req.send().await + .with_ctx(|| format!("Failed to write to {path}"))?; if !resp.status().is_success() { let status = resp.status(); let body = resp.text().await.unwrap_or_default(); @@ -387,34 +260,8 @@ impl BaoClient { } } - /// Read from an arbitrary API path. - pub async fn read(&self, path: &str) -> Result> { - let resp = self - .request(reqwest::Method::GET, path) - .send() - .await - .with_ctx(|| format!("Failed to read {path}"))?; - - if resp.status().as_u16() == 404 { - return Ok(None); - } - if !resp.status().is_success() { - let status = resp.status(); - let body = resp.text().await.unwrap_or_default(); - bail!("Read {path} returned {status}: {body}"); - } - - let body = resp.text().await.unwrap_or_default(); - if body.is_empty() { - Ok(Some(serde_json::Value::Null)) - } else { - Ok(Some(serde_json::from_str(&body)?)) - } - } - // ── Database secrets engine ───────────────────────────────────────── - /// Configure the database secrets engine connection. pub async fn write_db_config( &self, name: &str, @@ -435,7 +282,6 @@ impl BaoClient { Ok(()) } - /// Create a database static role. pub async fn write_db_static_role( &self, name: &str, @@ -450,8 +296,7 @@ impl BaoClient { "rotation_period": rotation_period, "rotation_statements": rotation_statements, }); - self.write(&format!("database/static-roles/{name}"), &data) - .await?; + self.write(&format!("database/static-roles/{name}"), &data).await?; Ok(()) } } @@ -461,39 +306,27 @@ mod tests { use super::*; #[test] - fn test_client_url_construction() { + fn test_new_client() { let client = BaoClient::new("http://localhost:8200"); - assert_eq!(client.url("sys/seal-status"), "http://localhost:8200/v1/sys/seal-status"); - assert_eq!(client.url("/sys/seal-status"), "http://localhost:8200/v1/sys/seal-status"); - } - - #[test] - fn test_client_url_strips_trailing_slash() { - let client = BaoClient::new("http://localhost:8200/"); assert_eq!(client.base_url, "http://localhost:8200"); } #[test] fn test_with_token() { let client = BaoClient::with_token("http://localhost:8200", "mytoken"); - assert_eq!(client.token, Some("mytoken".to_string())); + assert_eq!(client.inner.settings().token, "mytoken"); } #[test] - fn test_new_has_no_token() { - let client = BaoClient::new("http://localhost:8200"); - assert!(client.token.is_none()); + fn test_strips_trailing_slash() { + let client = BaoClient::new("http://localhost:8200/"); + assert_eq!(client.base_url, "http://localhost:8200"); } #[tokio::test] async fn test_seal_status_error_on_nonexistent_server() { - // Connecting to a port where nothing is listening should produce an - // error (connection refused), not a panic or hang. let client = BaoClient::new("http://127.0.0.1:19999"); let result = client.seal_status().await; - assert!( - result.is_err(), - "seal_status should return an error when the server is unreachable" - ); + assert!(result.is_err()); } } diff --git a/src/workflows/seed/steps/openbao_init.rs b/src/workflows/seed/steps/openbao_init.rs index d59a2dba..d7791d75 100644 --- a/src/workflows/seed/steps/openbao_init.rs +++ b/src/workflows/seed/steps/openbao_init.rs @@ -164,7 +164,7 @@ impl StepBody for InitOrUnsealOpenBao { let mut root_token = String::new(); let status = status.unwrap_or_else(|| crate::openbao::SealStatusResponse { - initialized: false, sealed: true, progress: 0, t: 0, n: 0, + initialized: false, sealed: true, }); // Check if truly initialized (not just a placeholder secret) @@ -213,7 +213,7 @@ impl StepBody for InitOrUnsealOpenBao { // Unseal if needed let status = bao.seal_status().await.unwrap_or_else(|_| { crate::openbao::SealStatusResponse { - initialized: true, sealed: true, progress: 0, t: 0, n: 0, + initialized: true, sealed: true, } }); if status.sealed && !unseal_key.is_empty() {