diff --git a/Cargo.lock b/Cargo.lock index 31d91b60..b6c26d35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,64 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.6", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes 0.8.4", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aes-kw" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ac571010bd60765c56085a4f1d412012a9be2663b1a2f2b19b49318653fd0d" +dependencies = [ + "aes 0.9.1", + "const-oid 0.10.2", + "zeroize", +] + [[package]] name = "ahash" version = "0.8.12" @@ -146,6 +204,18 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "async-broadcast" version = "0.7.2" @@ -216,6 +286,29 @@ dependencies = [ "uuid", ] +[[package]] +name = "attester" +version = "0.1.0" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=1fcebcb66a3c21b62e852819d5b55212c90eba2a#1fcebcb66a3c21b62e852819d5b55212c90eba2a" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "cfg-if", + "crypto", + "hex", + "kbs-types", + "num-traits", + "serde", + "serde_json", + "serde_with", + "sha2 0.11.0", + "strum 0.28.0", + "thiserror 2.0.20", + "tokio", + "tracing", +] + [[package]] name = "auto_impl" version = "1.3.0" @@ -276,6 +369,30 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "aws-lc-rs" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "axum" version = "0.8.9" @@ -415,6 +532,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "binstring" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef" + [[package]] name = "bitflags" version = "1.3.2" @@ -427,6 +550,17 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -445,6 +579,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -463,13 +606,25 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cbor-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e083a023562b37c52837e850131a51b1154cceb9d149f41ee3d386737b140f46" +dependencies = [ + "byteorder", + "libc", +] + [[package]] name = "cc" -version = "1.2.45" +version = "1.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -479,6 +634,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -493,6 +659,33 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -500,7 +693,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.6", - "inout", + "inout 0.1.4", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", ] [[package]] @@ -560,6 +763,26 @@ dependencies = [ "serde", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "coarsetime" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -600,6 +823,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "concat-kdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d72c1252426a83be2092dd5884a5f6e3b8e7180f6891b6263d2c21b92ec8816" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -641,6 +873,41 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -667,6 +934,23 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cose-rust" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a140f41f55ff1f2126aed96961bad2387ae31d7f9bbd0e98ec888073beaac6f" +dependencies = [ + "cbor-codec", + "openssl", + "rand 0.8.5", +] + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -700,6 +984,37 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto" +version = "0.1.0" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=1fcebcb66a3c21b62e852819d5b55212c90eba2a#1fcebcb66a3c21b62e852819d5b55212c90eba2a" +dependencies = [ + "aes-gcm", + "aes-kw", + "anyhow", + "base64 0.22.1", + "concat-kdf", + "ctr", + "kbs-types", + "openssl", + "p256", + "p521", + "rand 0.10.2", + "rand 0.8.5", + "rsa", + "serde", + "serde_json", + "sha2 0.11.0", + "strum 0.28.0", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -707,7 +1022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -719,6 +1034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -731,6 +1047,21 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "ct-codecs" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fb0c6640b4507ebd99ff67677009e381ba5eee1d14df78de4a3d16eb123c39" + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -797,16 +1128,6 @@ dependencies = [ "darling_macro 0.20.11", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - [[package]] name = "darling" version = "0.23.0" @@ -831,20 +1152,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", -] - [[package]] name = "darling_core" version = "0.23.0" @@ -871,26 +1178,47 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.21.3", + "darling_core 0.23.0", "quote", "syn 2.0.117", ] [[package]] -name = "darling_macro" -version = "0.23.0" +name = "defmt" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" dependencies = [ - "darling_core 0.23.0", + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] + [[package]] name = "der" version = "0.7.10" @@ -904,11 +1232,10 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -997,12 +1324,46 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ear" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aec2877d084955915f48086ae07f49f4c89e33e3826d1f7af33b342274f8100" +dependencies = [ + "base64 0.22.1", + "ciborium", + "cose-rust", + "hex", + "jsonwebtoken", + "lazy_static", + "openssl", + "phf", + "serde", + "serde_json", + "thiserror 2.0.20", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -1027,6 +1388,16 @@ dependencies = [ "signature", ] +[[package]] +name = "ed25519-compact" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c24599140dc39d7a81e4476e7573d41bbc18e07c803900298e522a5fbcfbfb6" +dependencies = [ + "ct-codecs", + "getrandom 0.4.1", +] + [[package]] name = "ed25519-dalek" version = "2.2.0" @@ -1074,7 +1445,7 @@ dependencies = [ "hkdf", "pem-rfc7468", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1190,7 +1561,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1202,9 +1573,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" @@ -1368,8 +1739,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1393,10 +1766,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", + "rand_core 0.10.1", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -1411,6 +1787,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.32.3" @@ -1437,6 +1823,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "git2" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +dependencies = [ + "bitflags 2.10.0", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "glob" version = "0.3.4" @@ -1472,7 +1871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1514,6 +1913,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1588,6 +1998,30 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac-sha1-compact" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b3ba31f6dc772cc8221ce81dbbbd64fa1e668255a6737d95eeace59b5a8823" + +[[package]] +name = "hmac-sha256" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019ece39bbefc17f13f677a690328cb978dbf6790e141a3c24e66372cb38588b" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "hostname" version = "0.4.1" @@ -2019,6 +2453,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -2052,6 +2495,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" +[[package]] +name = "is_debug" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe266d2e243c931d8190177f20bf7f24eed45e96f39e87dc49a27b32d12d407" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2093,28 +2542,56 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "34f877a98676d2fb664698d74cc6a51ce6c484ce8c770f05d0108ec9090aeb46" dependencies = [ + "defmt", "jiff-static", + "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", + "windows-link 0.2.1", ] [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "0666b5ab5ecaca213fc2a85b8c0083d9004e84ee2d5f9a7e0017aaf50986f25f" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.1", + "libc", +] + [[package]] name = "js-sys" version = "0.3.85" @@ -2172,10 +2649,11 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.3.0" +version = "10.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" dependencies = [ + "aws-lc-rs", "base64 0.22.1", "ed25519-dalek", "getrandom 0.2.16", @@ -2183,12 +2661,65 @@ dependencies = [ "js-sys", "p256", "p384", - "rand", + "pem", + "rand 0.8.5", "rsa", "serde", "serde_json", "sha2 0.10.9", "signature", + "simple_asn1", + "zeroize", +] + +[[package]] +name = "jsonwebtoken-openssl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53aec0291d5d8b37fbf826ccc23f5473536dad8dafda4161a7b6b56c2b0e0202" +dependencies = [ + "jsonwebtoken", + "openssl", +] + +[[package]] +name = "jwt-simple" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad8761f175784dfbb83709f322fc4daf6b27afd5bf375492f2876f9e925ef5a" +dependencies = [ + "anyhow", + "binstring", + "blake2b_simd", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand 0.8.5", + "serde", + "serde_json", + "superboring", + "thiserror 2.0.20", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.9", + "signature", ] [[package]] @@ -2210,10 +2741,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c6922f6afe80418dd6019818af5d0d34584c371780ff09b9752370c25b4abb" dependencies = [ "base64 0.22.1", - "jiff", - "schemars 1.1.0", + "jiff", + "schemars 1.1.0", + "serde", + "serde_json", +] + +[[package]] +name = "kbs-client" +version = "0.1.0" +source = "git+https://github.com/confidential-containers/trustee.git?rev=e65897a9ad4eb3ac69fa2ec75ed831200eb2acd7#e65897a9ad4eb3ac69fa2ec75ed831200eb2acd7" +dependencies = [ + "anyhow", + "base64 0.22.1", + "clap", + "jsonwebtoken", + "kbs_protocol", + "reqwest 0.13.2", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "kbs-types" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010924a5f65328c9609598c895ea506c723e3c72e528aeaba0a9645d8330b718" +dependencies = [ + "base64 0.22.1", + "ear", + "serde", + "serde_json", + "sha2 0.10.9", + "sm3", + "strum 0.27.2", + "thiserror 2.0.20", +] + +[[package]] +name = "kbs_protocol" +version = "0.1.0" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=1fcebcb66a3c21b62e852819d5b55212c90eba2a#1fcebcb66a3c21b62e852819d5b55212c90eba2a" +dependencies = [ + "anyhow", + "async-trait", + "attester", + "base64 0.22.1", + "crypto", + "jwt-simple", + "kbs-types", + "reqwest 0.13.2", + "resource_uri", "serde", "serde_json", + "serde_json_canonicalizer", + "sha2 0.11.0", + "shadow-rs", + "thiserror 2.0.20", + "tokio", + "tracing", + "url", + "zeroize", ] [[package]] @@ -2450,12 +3041,36 @@ version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +[[package]] +name = "libgit2-sys" +version = "0.18.5+1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libz-sys" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "lief" version = "0.17.6" @@ -2532,6 +3147,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2547,6 +3168,15 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "matchit" version = "0.8.4" @@ -2650,6 +3280,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2662,25 +3301,25 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ "lazy_static", "libm", "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -2816,6 +3455,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.81" @@ -2853,6 +3498,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-src" +version = "300.6.1+3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46eb8fb9fb3b61ce1c0f8a026c4c1a0714d3a9e138e7fbde78753ce2babc3846" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.117" @@ -2861,6 +3515,7 @@ checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -2880,7 +3535,10 @@ dependencies = [ "http 1.5.0", "json-patch", "jsonptr 0.8.1", + "jsonwebtoken", + "jsonwebtoken-openssl", "k8s-openapi 0.28.0", + "kbs-client", "kube 4.2.0", "log", "oci-client", @@ -2944,7 +3602,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "rand_core", + "rand_core 0.6.4", "sha2 0.10.9", ] @@ -3045,6 +3703,49 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -3104,6 +3805,18 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -3217,6 +3930,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + [[package]] name = "quote" version = "1.0.45" @@ -3240,7 +3969,18 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.1", + "rand_core 0.10.1", ] [[package]] @@ -3250,7 +3990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3262,6 +4002,12 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3392,6 +4138,8 @@ checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", + "cookie", + "cookie_store", "futures-core", "futures-util", "http 1.5.0", @@ -3423,6 +4171,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "resource_uri" +version = "0.1.0" +source = "git+https://github.com/confidential-containers/guest-components.git?rev=1fcebcb66a3c21b62e852819d5b55212c90eba2a#1fcebcb66a3c21b62e852819d5b55212c90eba2a" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "url", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -3443,15 +4202,15 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid 0.9.6", "digest 0.10.7", @@ -3460,7 +4219,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sha2 0.10.9", "signature", "spki", @@ -3560,7 +4319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3571,7 +4330,7 @@ checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3586,6 +4345,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "ryu-js" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d056b875a9d2e6cb9a61d127afee9ac5999b9f87bcb32079d1318e505be714" + [[package]] name = "schannel" version = "0.1.28" @@ -3645,7 +4410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3806,6 +4571,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_json_canonicalizer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe52319a927259afbfa5180c5157cd8167edfd3e8c254f9558c7fef44c5649f2" +dependencies = [ + "ryu-js", + "serde", + "serde_json", +] + [[package]] name = "serde_path_to_error" version = "0.1.20" @@ -3840,11 +4616,12 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64 0.22.1", + "bs58", "chrono", "hex", "indexmap 1.9.3", @@ -3859,11 +4636,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -3904,11 +4681,32 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "shadow-rs" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd39b4b2077bd36e60ca28c31d494046e747759cb9b507a7d177bb64787c39e" +dependencies = [ + "const_format", + "git2", + "is_debug", + "jiff", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -3926,7 +4724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3935,12 +4733,39 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.20", + "time", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "sm3" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebb9a3b702d0a7e33bc4d85a14456633d2b165c2ad839c5fd9a8417c1ab15860" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -3995,7 +4820,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" dependencies = [ - "cipher", + "cipher 0.4.4", "ssh-encoding", ] @@ -4020,7 +4845,7 @@ dependencies = [ "p256", "p384", "p521", - "rand_core", + "rand_core 0.6.4", "rsa", "sec1", "sha2 0.10.9", @@ -4091,6 +4916,19 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "superboring" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f" +dependencies = [ + "getrandom 0.2.16", + "hmac-sha256", + "hmac-sha512", + "rand 0.8.5", + "rsa", +] + [[package]] name = "supports-color" version = "2.1.0" @@ -4273,32 +5111,40 @@ dependencies = [ "syn 3.0.2", ] +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" -version = "0.3.44" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -4503,9 +5349,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -4515,9 +5361,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -4526,11 +5372,41 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -4568,7 +5444,7 @@ dependencies = [ "kube 4.2.0", "log", "percent-encoding", - "rand_core", + "rand_core 0.6.4", "serde", "serde_json", "serde_yaml", @@ -4586,6 +5462,7 @@ version = "0.2.2" dependencies = [ "anyhow", "cfg-if", + "chrono", "compute-pcrs-lib", "hex", "k8s-openapi 0.28.0", @@ -4681,12 +5558,28 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.6", + "subtle", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -4729,6 +5622,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4774,6 +5673,15 @@ dependencies = [ "wit-bindgen 0.51.0", ] +[[package]] +name = "wasix" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae86f02046da16a333a9129d31451423e1657737ecdafed4193838a5f54c5cfe" +dependencies = [ + "wasi", +] + [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -5399,6 +6307,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "zerotrie" diff --git a/Makefile b/Makefile index 0a65d4dc..f5356064 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,8 @@ OPERATOR_IMAGE ?= $(REGISTRY)/trusted-cluster-operator:$(TAG) COMPUTE_PCRS_IMAGE=$(REGISTRY)/compute-pcrs:$(TAG) REG_SERVER_IMAGE=$(REGISTRY)/registration-server:$(TAG) ATTESTATION_KEY_REGISTER_IMAGE=$(REGISTRY)/attestation-key-register:$(TAG) -TRUSTEE_IMAGE ?= quay.io/trusted-execution-clusters/key-broker-service:v0.17.0 + +TRUSTEE_IMAGE ?= quay.io/trusted-execution-clusters/key-broker-service:v0.20.0 TEST_IMAGE ?= quay.io/trusted-execution-clusters/fedora-coreos-kubevirt:42.20260622 # tagged as 42.20251012.2.0 APPROVED_IMAGE ?= quay.io/trusted-execution-clusters/fedora-coreos@sha256:6997f51fd27d1be1b5fc2e6cc3ebf16c17eb94d819b5d44ea8d6cf5f826ee773 diff --git a/operator/Cargo.toml b/operator/Cargo.toml index c813d24c..60804910 100644 --- a/operator/Cargo.toml +++ b/operator/Cargo.toml @@ -31,6 +31,9 @@ serde_json.workspace = true thiserror = "2.0.18" tokio.workspace = true toml = "1.1.2" +kbs-client = {git = "https://github.com/confidential-containers/trustee.git", rev = "e65897a9ad4eb3ac69fa2ec75ed831200eb2acd7", default-features = false, features = ["native-tls"] } +jsonwebtoken = { version = "10.4.0", default-features = false, features = ["use_pem"] } +jsonwebtoken-openssl = "1.0.0" [dev-dependencies] http.workspace = true diff --git a/operator/src/attestation_key_register.rs b/operator/src/attestation_key_register.rs index cd502ae4..a8c9efc3 100644 --- a/operator/src/attestation_key_register.rs +++ b/operator/src/attestation_key_register.rs @@ -211,7 +211,7 @@ async fn machine_reconcile( // Check if the machine is being deleted if machine.metadata.deletion_timestamp.is_some() { info!( - "Machine {} is being deleted, skipping update of attestation key volumes", + "Machine {} is being deleted, updating attestation key", machine.metadata.name.clone().unwrap_or_default() ); return Ok(LONG_REQUEUE); @@ -328,12 +328,13 @@ async fn secret_reconcile( finalizer(&secrets, ATTESTATION_KEY_SECRET_FINALIZER, secret, |ev| async move { match ev { Event::Apply(_secret) => { - // On creation/update, just update the trustee deployment volumes - trustee::update_attestation_keys(&ctx) + // On creation/update, just update the AK via trustee API + trustee::update_attestation_keys(ctx.client.clone()) .await .map(|_| LONG_REQUEUE) .map_err(|e| { warn!("Error updating attestation key volumes on secret apply: {e}"); + warn!("Error updating attestation key on secret apply: {e}"); finalizer::Error::::ApplyFailed(e.into()) }) } @@ -343,11 +344,13 @@ async fn secret_reconcile( "AttestationKey secret {secret_name} is being deleted, updating trustee deployment volumes" ); // Update trustee deployment - secrets with deletion_timestamp will be filtered out - trustee::update_attestation_keys(&ctx) + trustee::update_attestation_keys(ctx.client.clone()) .await .map(|_| LONG_REQUEUE) .map_err(|e| { - warn!("Error updating attestation key volumes during secret deletion: {e}"); + warn!( + "Error updating attestation key during secret deletion: {e}" + ); finalizer::Error::::CleanupFailed(e.into()) }) } diff --git a/operator/src/kbs-config.toml b/operator/src/kbs-config.toml index fc1005d5..81296d53 100644 --- a/operator/src/kbs-config.toml +++ b/operator/src/kbs-config.toml @@ -2,36 +2,35 @@ sockets = ["0.0.0.0:8080"] [admin] -type = "DenyAll" +authorization_mode = "AuthenticatedAuthorization" + + [admin.authentication.bearer_jwt] + identity_providers = [ + { public_key_uri = "/opt/trustee/keys/public.pub" } + ] + + [admin.authorization.regex_acl] + acls = [{ role = "admin", allowed_endpoints = "^/kbs/.+$" }] [attestation_token] -insecure_key = true -attestation_token_type = "CoCo" +insecure_header_jwk = true [attestation_service] type = "coco_as_builtin" -work_dir = "/opt/trustee" -policy_engine = "opa" +timeout = 5 [attestation_service.attestation_token_broker] - type = "Ear" - policy_dir = "/opt/trustee/policies" - - [attestation_service.attestation_token_config] duration_min = 5 [attestation_service.rvps_config] type = "BuiltIn" - [attestation_service.rvps_config.storage] - type = "LocalJson" - file_path = "/opt/trustee/reference-values.json" - - [[plugins]] name = "resource" -type = "LocalFs" -dir_path = "/opt/trustee/kbs-repository" +storage_backend_type = "kvstorage" + +[storage_backend] +storage_type = "LocalJson" -[policy_engine] -policy_path = "/opt/trustee/policy.rego" + [storage_backend.backends.local_json] + file_dir_path = "/opt/trustee/storage" \ No newline at end of file diff --git a/operator/src/main.rs b/operator/src/main.rs index 18d59665..e7ce91ea 100644 --- a/operator/src/main.rs +++ b/operator/src/main.rs @@ -28,12 +28,11 @@ mod register_server; #[cfg(test)] mod test_utils; mod trustee; - use crate::conditions::*; use operator::*; /// Default fallback version tag for Trustee image if RELATED_IMAGE_TRUSTEE is not set. -const TRUSTEE_VERSION: &str = "v0.17.0"; +const TRUSTEE_VERSION: &str = "v0.20.0"; /// Default fallback version tag for operator-managed component images from compile time environment variable (comes from operator crate Cargo.toml) const COMPONENT_VERSION: &str = match option_env!("COMPONENT_VERSION") { @@ -172,11 +171,14 @@ async fn install_trustee_configuration( .context("Failed to create the KBS configuration configmap")?; info!("Generated configmap for the KBS configuration"); - trustee::generate_attestation_policy(client.clone(), owner_reference.clone()) + trustee::generate_trustee_auth_keys_secret(client.clone(), owner_reference.clone()) .await - .context("Failed to create the attestation policy configmap")?; - info!("Generated configmap for the attestation policy"); - + .context("Failed to create the auth keys")?; + info!("Generate auth keys for the KBS API"); + trustee::generate_rv_data(client.clone(), owner_reference.clone()) + .await + .context("Failed to create the reference values configmap")?; + info!("Created configmap for reference values"); let kbs_port = cluster.spec.trustee_kbs_port; trustee::generate_kbs_service(client.clone(), owner_reference.clone(), kbs_port) .await @@ -252,7 +254,7 @@ async fn install_attestation_key_register( #[tokio::main] async fn main() -> Result<()> { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - + let _ = jsonwebtoken_openssl::install_default(); let kube_client = Client::try_default().await?; info!("trusted execution clusters operator",); @@ -286,6 +288,7 @@ async fn main() -> Result<()> { reference_values::create_pcrs_config_map(kube_client.clone()).await?; reference_values::launch_rv_image_controller(kube_client.clone()).await; reference_values::launch_rv_job_controller(kube_client.clone()).await; + trustee::launch_trustee_sync_controller(kube_client.clone()).await; Controller::new(cl, watcher::Config::default()) .run(reconcile, controller_error_policy, ctx) @@ -299,7 +302,7 @@ async fn main() -> Result<()> { mod tests { use http::{Method, Request, StatusCode}; use k8s_openapi::api::apps::v1::Deployment; - use k8s_openapi::api::core::v1::{ConfigMap, Service}; + use k8s_openapi::api::core::v1::{ConfigMap, Secret, Service}; use k8s_openapi::{apimachinery::pkg::apis::meta::v1::Time, jiff::Timestamp}; use kube::api::ObjectList; use kube::client::Body; @@ -441,31 +444,32 @@ mod tests { }; let clos = async |req: Request, ctr| { - if ctr < 8 && req.method() == Method::POST { + if ctr < 9 && req.method() == Method::POST { use serde_json::to_string; let resp = match ctr { - // Trustee - 0 => to_string(&ConfigMap::default()), - 1 => to_string(&ConfigMap::default()), - 2 => to_string(&Service::default()), - 3 => to_string(&Deployment::default()), - // Registration server - 4 => to_string(&Deployment::default()), - 5 => to_string(&Service::default()), - // Attestation key register server - 6 => to_string(&Deployment::default()), - 7 => to_string(&Service::default()), + // install_trustee_configuration + 0 => to_string(&ConfigMap::default()), // trustee-data + 1 => to_string(&Secret::default()), // trustee-auth + 2 => to_string(&ConfigMap::default()), // trustee-rv-data + 3 => to_string(&Service::default()), // kbs-service + 4 => to_string(&Deployment::default()), // trustee-deployment + // install_register_server + 5 => to_string(&Deployment::default()), + 6 => to_string(&Service::default()), + // install_attestation_key_register + 7 => to_string(&Deployment::default()), + 8 => to_string(&Service::default()), _ => unreachable!("unexpected counter {ctr}"), }; Ok(resp.unwrap()) - } else if ctr == 8 && req.method() == Method::GET { + } else if ctr == 9 && req.method() == Method::GET { let object_list = ObjectList:: { items: Vec::new(), types: Default::default(), metadata: Default::default(), }; Ok(serde_json::to_string(&object_list).unwrap()) - } else if ctr == 9 && req.method() == Method::PATCH { + } else if ctr == 10 && req.method() == Method::PATCH { let body = req.into_body().collect_bytes().await.unwrap().to_vec(); let body = String::from_utf8_lossy(&body); assert!(body.contains("ForeignCondition"),); @@ -496,7 +500,7 @@ mod tests { cluster.status = Some(TrustedExecutionClusterStatus { conditions: Some(vec![pre_existing_installed, foreign_condition]), }); - count_check!(10, clos, |client| { + count_check!(11, clos, |client| { let result = reconcile(Arc::new(cluster), Arc::new(dummy_cluster_ctx(client))).await; assert_eq!(result.unwrap(), LONG_REQUEUE); }); diff --git a/operator/src/reference_values.rs b/operator/src/reference_values.rs index 946315c2..3350de0a 100644 --- a/operator/src/reference_values.rs +++ b/operator/src/reference_values.rs @@ -495,12 +495,13 @@ mod tests { Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap()) } (2, &Method::GET) | (3, &Method::PUT) => { - assert!(req.uri().path().contains(trustee::TRUSTEE_DATA_MAP)); + assert!(req.uri().path().contains(trustee::TRUSTEE_RV_MAP)); Ok(serde_json::to_string(&dummy_trustee_map()).unwrap()) } + (4, &Method::GET) => Err(StatusCode::NOT_FOUND), _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; - count_check!(4, clos, |client| { + count_check!(5, clos, |client| { let job = Arc::new(dummy_job()); let result = job_reconcile(job, Arc::new(client)).await.unwrap(); assert_eq!(result, Action::await_change()); @@ -612,12 +613,13 @@ mod tests { Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap()) } (3, &Method::GET) | (4, &Method::PUT) => { - assert!(req.uri().path().contains(trustee::TRUSTEE_DATA_MAP)); + assert!(req.uri().path().contains(trustee::TRUSTEE_RV_MAP)); Ok(serde_json::to_string(&dummy_trustee_map()).unwrap()) } + (5, &Method::GET) => Err(StatusCode::NOT_FOUND), _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; - count_check!(5, clos, |client| { + count_check!(6, clos, |client| { assert!(image_remove_reconcile(client, image, cluster).await.is_ok()); }); } diff --git a/operator/src/register_server.rs b/operator/src/register_server.rs index 8b733ef6..3ee1351c 100644 --- a/operator/src/register_server.rs +++ b/operator/src/register_server.rs @@ -139,7 +139,7 @@ async fn keygen_reconcile( async { let owner_reference = generate_owner_reference(&Arc::unwrap_or_clone(machine))?; trustee::generate_secret(kube_client.clone(), id, owner_reference).await?; - trustee::mount_secret(kube_client, id).await + trustee::send_secret(kube_client, id).await } .await .map(|_| LONG_REQUEUE) @@ -184,8 +184,7 @@ async fn keygen_reconcile( } } } - - trustee::unmount_secret(kube_client, id) + trustee::delete_secret(kube_client, id) .await .map(|_| LONG_REQUEUE) .map_err(|e| finalizer::Error::::CleanupFailed(e.into())) diff --git a/operator/src/test_utils.rs b/operator/src/test_utils.rs index f21fecb7..9c14c4c6 100644 --- a/operator/src/test_utils.rs +++ b/operator/src/test_utils.rs @@ -2,13 +2,18 @@ // // SPDX-License-Identifier: MIT +use crate::trustee; use compute_pcrs_lib::Pcr; use compute_pcrs_lib::tpmevents::{TPMEvent, TPMEventID}; -use k8s_openapi::{api::core::v1::ConfigMap, jiff::Timestamp}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; +use k8s_openapi::{ + api::core::v1::{ConfigMap, Secret}, + jiff::Timestamp, +}; +use kube::api::ObjectMeta; use std::collections::BTreeMap; - -use crate::trustee; use trusted_cluster_operator_lib::reference_values::{ImagePcr, ImagePcrs, PCR_CONFIG_FILE}; +use trusted_cluster_operator_lib::{Machine, MachineSpec}; pub const DUMMY_PCR_4_VALUE: &str = "3f263b96ccbc33bb53d808771f9ab1e02d4dec8854f9530f749cde853a723273"; @@ -53,6 +58,26 @@ pub fn dummy_pcrs() -> ImagePcrs { )])) } +pub fn dummy_trustee_auth() -> Secret { + let key_pair = + trustee::generate_ed25519_key_pair().expect("Failed to generate ed25519 key pair"); + let data = BTreeMap::from([ + ( + trustee::TRUSTEE_AUTH_PRIV_KEY.to_string(), + k8s_openapi::ByteString(key_pair.private_key_pem), + ), + ( + trustee::TRUSTEE_AUTH_PUB_KEY.to_string(), + k8s_openapi::ByteString(key_pair.public_key_pem), + ), + ]); + + Secret { + data: Some(data), + ..Default::default() + } +} + pub fn dummy_trustee_map() -> ConfigMap { ConfigMap { data: Some(BTreeMap::from([( @@ -73,3 +98,34 @@ pub fn dummy_pcrs_map() -> ConfigMap { ..Default::default() } } + +pub fn dummy_machine(id: &str) -> Machine { + Machine { + metadata: ObjectMeta { + name: Some(id.to_string()), + ..Default::default() + }, + spec: MachineSpec { id: id.to_string() }, + status: None, + } +} + +pub fn dummy_ak_secret(name: &str) -> Secret { + Secret { + metadata: ObjectMeta { + name: Some(name.to_string()), + owner_references: Some(vec![OwnerReference { + kind: "AttestationKey".to_string(), + name: name.to_string(), + uid: "ak-uid".to_string(), + ..Default::default() + }]), + ..Default::default() + }, + data: Some(BTreeMap::from([( + "public_key".to_string(), + k8s_openapi::ByteString(b"test-ak-public-key".to_vec()), + )])), + ..Default::default() + } +} diff --git a/operator/src/tpm.rego b/operator/src/tpm.rego index abf6ea6d..f94b74a5 100644 --- a/operator/src/tpm.rego +++ b/operator/src/tpm.rego @@ -9,6 +9,7 @@ executables := 3 if { input.tpm.pcr04 in query_reference_value("tpm_pcr4") input.tpm.pcr14 in query_reference_value("tpm_pcr14") + input.tpm.ak_public in query_reference_value("trusted_aks") } # Azure SNP vTPM validation executables := 3 if { diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index 1000af65..f06b537c 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -4,18 +4,18 @@ // // SPDX-License-Identifier: MIT -use crate::attestation_key_register::AkContextData; use anyhow::{Context, Result}; use base64::{Engine as _, engine::general_purpose}; use chrono::{DateTime, Utc}; use clevis_pin_trustee_lib::Key as ClevisKey; use compute_pcrs_lib::tpmevents::TPMEvent; use compute_pcrs_lib::tpmevents::combine::combine_images; +use futures_util::StreamExt; use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec}; use k8s_openapi::api::core::v1::{ ConfigMap, ConfigMapVolumeSource, Container, ContainerPort, EmptyDirVolumeSource, EnvVar, - KeyToPath, PodSpec, PodTemplateSpec, ProjectedVolumeSource, Secret, SecretProjection, - SecretVolumeSource, Service, ServicePort, ServiceSpec, Volume, VolumeMount, VolumeProjection, + KeyToPath, PodSpec, PodTemplateSpec, Secret, SecretVolumeSource, Service, ServicePort, + ServiceSpec, Volume, VolumeMount, }; use k8s_openapi::apimachinery::pkg::{ apis::meta::v1::{LabelSelector, OwnerReference}, @@ -23,27 +23,40 @@ use k8s_openapi::apimachinery::pkg::{ }; use kube::{ Api, Client, Resource, - api::{ObjectMeta, Patch, PatchParams}, - runtime::reflector::ObjectRef, + api::ObjectMeta, + runtime::{ + controller::{Action, Controller}, + watcher, + }, }; -use log::info; -use operator::{TLS_DIR, create_or_info_if_exists, read_certificate}; -use serde::{Serialize, Serializer}; +use log::{info, warn}; +use operator::{ + ControllerError, TLS_DIR, controller_error_policy, controller_info, create_or_info_if_exists, + read_certificate, +}; + +use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; +use serde::{Deserialize, Serialize, Serializer}; use serde_json::{Value::String as JsonString, json}; use std::collections::{BTreeMap, BTreeSet}; +use std::sync::Arc; +use std::time::Duration; -use trusted_cluster_operator_lib::endpoints::*; use trusted_cluster_operator_lib::reference_values::*; +use trusted_cluster_operator_lib::{Machine, endpoints::*}; -const TRUSTEE_DATA_DIR: &str = "/opt/trustee"; -pub const TRUSTEE_SECRETS_PATH: &str = "/opt/trustee/kbs-repository/default"; +const TRUSTEE_DATA_DIR: &str = "/etc/kbs"; const KBS_CONFIG_FILE: &str = "kbs-config.toml"; -pub(crate) const REFERENCE_VALUES_FILE: &str = "reference-values.json"; pub(crate) const TRUSTEE_DATA_MAP: &str = "trustee-data"; -const ATT_POLICY_MAP: &str = "attestation-policy"; -const TRUSTED_AK_KEYS_VOLUME: &str = "trusted-ak-keys"; -const TRUSTED_AK_KEYS_DIR: &str = "/etc/tpm/trusted_ak_keys"; +pub(crate) const TRUSTEE_RV_MAP: &str = "trustee-rv-data"; +pub(crate) const REFERENCE_VALUES_FILE: &str = "reference-values.json"; +const TRUSTEE_AUTH_SECRET: &str = "trustee-auth"; +const TRUSTEE_AUTH_KEY_DIR: &str = "/opt/trustee/keys"; +const TRUSTEE_STORAGE_VOLUME: &str = "trustee-storage"; +const TRUSTEE_STORAGE_DIR: &str = "/opt/trustee/storage"; +pub(crate) const TRUSTEE_AUTH_PUB_KEY: &str = "public.pub"; +pub(crate) const TRUSTEE_AUTH_PRIV_KEY: &str = "private.key"; fn primitive_date_time_to_str(d: &DateTime, s: S) -> Result where @@ -56,7 +69,7 @@ where /// reference_value_provider_service::reference_value::ReferenceValue /// (cannot import directly because its expiration doesn't serialize /// right) -#[derive(Serialize)] +#[derive(Serialize, Deserialize)] struct ReferenceValue { pub version: String, pub name: String, @@ -101,24 +114,211 @@ fn recompute_reference_values(image_pcrs: ImagePcrs) -> Vec { } pub async fn update_reference_values(client: Client) -> Result<()> { - let config_maps: Api = Api::default_namespaced(client); + let config_maps: Api = Api::default_namespaced(client.clone()); let image_pcrs_map = config_maps.get(PCR_CONFIG_MAP).await?; let reference_values = recompute_reference_values(get_image_pcrs(image_pcrs_map)?); let rv_json = serde_json::to_string(&reference_values)?; - let mut trustee_map = config_maps.get(TRUSTEE_DATA_MAP).await?; - let err = format!("ConfigMap {TRUSTEE_DATA_MAP} existed, but had no data"); - let trustee_data = trustee_map.data.as_mut().context(err)?; - trustee_data.insert(REFERENCE_VALUES_FILE.to_string(), rv_json); - + let mut rv_map = config_maps.get(TRUSTEE_RV_MAP).await?; + let err = format!("ConfigMap {TRUSTEE_RV_MAP} existed, but had no data"); + let rv_data = rv_map.data.as_mut().context(err)?; + rv_data.insert(REFERENCE_VALUES_FILE.to_string(), rv_json); config_maps - .replace(TRUSTEE_DATA_MAP, &Default::default(), &trustee_map) + .replace(TRUSTEE_RV_MAP, &Default::default(), &rv_map) .await?; + + if let Err(e) = sync_reference_values(&client, &reference_values).await { + warn!( + "Failed to sync reference values to KBS (will retry on next deployment reconcile): {e}" + ); + } info!("Recomputed reference values"); Ok(()) } +async fn get_auth_key_token(client: &Client) -> Result { + let secret_api: Api = Api::default_namespaced(client.clone()); + let auth_secret = secret_api.get(TRUSTEE_AUTH_SECRET).await?; + let auth_data = auth_secret.data.context("Auth secret has no data")?; + let auth_key_bytes = auth_data + .get(TRUSTEE_AUTH_PRIV_KEY) + .context("Auth secret missing private.key")?; + + let claims = json!({ + "role": "admin", + "exp": i32::MAX + }); + + let encoding_key = EncodingKey::from_ed_pem(auth_key_bytes.0.as_slice())?; + + let token = encode(&Header::new(Algorithm::EdDSA), &claims, &encoding_key)?; + Ok(token) +} +async fn get_kbs_connection(client: &Client) -> Result<(String, Vec)> { + let tec = trusted_cluster_operator_lib::get_trusted_execution_cluster(client.clone()).await?; + let secret_api: Api = Api::default_namespaced(client.clone()); + + if let Some(secret_name) = &tec.spec.trustee_secret + && let Ok(secret) = secret_api.get(secret_name).await + && let Some(ca_crt) = secret.data.as_ref().and_then(|d| d.get("ca.crt")) + { + let ca_pem = + String::from_utf8(ca_crt.0.clone()).context("ca certificate is not valid UTF-8")?; + let trustee_addr = format!( + "https://{}", + tec.spec + .public_trustee_addr + .as_ref() + .context("TrustedExecutionCluster missing public_trustee_addr HTTPS")? + ); + return Ok((trustee_addr, vec![ca_pem])); + } + + Ok(( + format!( + "http://{}", + tec.spec + .public_trustee_addr + .as_ref() + .context("TrustedExecutionCluster missing public_trustee_addr HTTP")? + ), + vec![], + )) +} + +async fn sync_reference_values(client: &Client, reference_values: &[ReferenceValue]) -> Result<()> { + let auth_token = get_auth_key_token(client).await?; + let (url, certs) = get_kbs_connection(client).await?; + for rv in reference_values { + kbs_client::set_sample_rv( + url.clone(), + rv.name.clone(), + rv.value.clone(), + Some(auth_token.clone()), + certs.clone(), + ) + .await?; + } + info!("Sent {} reference values to KBS", reference_values.len()); + Ok(()) +} + +async fn sync_reference_values_from_configmap(client: &Client) -> Result<()> { + let config_maps: Api = Api::default_namespaced(client.clone()); + let rv_map = config_maps.get(TRUSTEE_RV_MAP).await?; + let data = rv_map.data.context("RV ConfigMap has no data")?; + let rv_json = match data.get(REFERENCE_VALUES_FILE) { + Some(json) => json, + None => { + info!("No reference values in ConfigMap yet, skipping sync"); + return Ok(()); + } + }; + let reference_values: Vec = serde_json::from_str(rv_json)?; + if reference_values.is_empty() { + return Ok(()); + } + sync_reference_values(client, &reference_values).await +} + +pub async fn sync_resource_policy(client: Client) -> Result<()> { + let auth_token = get_auth_key_token(&client).await?; + let (url, certs) = get_kbs_connection(&client).await?; + let policy = include_str!("resource.rego"); + info!("Sending resource policy to KBS API..."); + kbs_client::set_resource_policy(&url, Some(auth_token), policy.as_bytes().to_vec(), certs) + .await?; + info!("Resource policy set successfully"); + Ok(()) +} + +pub async fn sync_attestation_policy(client: Client) -> Result<()> { + let auth_token = get_auth_key_token(&client).await?; + let (url, certs) = get_kbs_connection(&client).await?; + let policy = include_str!("tpm.rego"); + info!("Sending attestation policy to KBS API..."); + kbs_client::set_attestation_policy( + &url, + Some(auth_token), + policy.as_bytes().to_vec(), + Some("rego".to_string()), + Some("default_cpu".to_string()), + certs, + ) + .await?; + info!("Attestation policy set successfully"); + Ok(()) +} + +async fn trustee_deployment_reconcile( + deployment: Arc, + client: Arc, +) -> Result { + if let Some(status) = &deployment.status + && let Some(is_available) = &status.conditions + && is_available + .iter() + .any(|c| c.type_ == "Available" && c.status == "True") + { + let c = Arc::unwrap_or_clone(client.clone()); + if let Err(e) = sync_resource_policy(c.clone()).await { + warn!("Failed to sync resource policy to KBS: {e}"); + return Ok(Action::requeue(Duration::from_secs(30))); + } + if let Err(e) = sync_attestation_policy(c.clone()).await { + warn!("Failed to sync attestation policy to KBS: {e}"); + return Ok(Action::requeue(Duration::from_secs(30))); + } + if let Err(e) = sync_reference_values_from_configmap(&c).await { + warn!("Failed to sync reference values to KBS: {e}"); + return Ok(Action::requeue(Duration::from_secs(30))); + } + if let Err(e) = sync_all_machine_luks_key(c.clone()).await { + warn!("Failed to sync machine luks keys to KBS: {e}"); + return Ok(Action::requeue(Duration::from_secs(30))); + } + if let Err(e) = update_attestation_keys(c).await { + warn!("Failed to update attestation keys to KBS: {e}"); + return Ok(Action::requeue(Duration::from_secs(30))); + } + } + + Ok(Action::await_change()) +} + +pub async fn launch_trustee_sync_controller(client: Client) { + let deployments: Api = Api::default_namespaced(client.clone()); + let watcher_config = watcher::Config { + label_selector: Some(format!("app={TRUSTEE_APP_LABEL}")), + ..Default::default() + }; + tokio::spawn( + Controller::new(deployments, watcher_config) + .run( + trustee_deployment_reconcile, + controller_error_policy, + Arc::new(client), + ) + .for_each(controller_info), + ); +} + +pub struct Ed25519KeyPair { + pub private_key_pem: Vec, + pub public_key_pem: Vec, +} + +pub fn generate_ed25519_key_pair() -> Result { + let key = openssl::pkey::PKey::generate_ed25519()?; + let private_key_pem = key.private_key_to_pem_pkcs8()?; + let public_key_pem = key.public_key_to_pem()?; + Ok(Ed25519KeyPair { + private_key_pem, + public_key_pem, + }) +} + fn generate_luks_key() -> Result> { // Constraint: 32 bytes b64-encoded, thus 24 let mut pass = [0; 24]; @@ -131,87 +331,93 @@ fn generate_luks_key() -> Result> { serde_json::to_vec(&jwk).map_err(Into::into) } -fn generate_secret_volume(id: &str) -> (Volume, VolumeMount) { - ( - Volume { - name: id.to_string(), - secret: Some(SecretVolumeSource { - secret_name: Some(id.to_string()), - ..Default::default() - }), - ..Default::default() - }, - VolumeMount { - name: id.to_string(), - mount_path: format!("{TRUSTEE_SECRETS_PATH}/{id}"), - ..Default::default() - }, - ) +pub fn secret_path(id: &str) -> String { + format!("default/{id}/root") } -pub async fn mount_secret(client: Client, id: &str) -> Result<()> { - let result = do_mount_secret(client, id, true).await; - info!("Mounted secret {id} to {TRUSTEE_DEPLOYMENT}"); - result +pub async fn send_secret(client: Client, id: &str) -> Result<()> { + let secret_api: Api = Api::default_namespaced(client.clone()); + let auth_key_token = get_auth_key_token(&client).await?; + let (url, certs) = get_kbs_connection(&client).await?; + let secret = secret_api.get(id).await?; + let secret_data = secret.data.context("Secret has no data")?; + let resource_bytes = secret_data + .get("root") + .context("Secret missing root key")? + .0 + .clone(); + let path = secret_path(id); + info!("Sending secret {id} to KBS API..."); + kbs_client::set_resource(&url, Some(auth_key_token), resource_bytes, &path, certs).await?; + info!("Secret {id} sent successfully"); + Ok(()) } -pub async fn unmount_secret(client: Client, id: &str) -> Result<()> { - let result = do_mount_secret(client, id, false).await; - info!("Unmounted secret {id} from {TRUSTEE_DEPLOYMENT}"); - result +pub async fn delete_secret(client: Client, id: &str) -> Result<()> { + let auth_key_token = get_auth_key_token(&client).await?; + let (url, certs) = get_kbs_connection(&client).await?; + let path = secret_path(id); + info!("Deleting secret {id} to KBS API..."); + kbs_client::delete_resource(&url, Some(auth_key_token), &path, certs).await?; + info!("Secret {id} deleted successfully"); + Ok(()) } -pub async fn do_mount_secret(client: Client, id: &str, add: bool) -> Result<()> { - let deployments: Api = Api::default_namespaced(client); - let mut deployment = deployments.get(TRUSTEE_DEPLOYMENT).await?; +pub async fn register_ak(client: Client, ak_secrets: &[String]) -> Result<()> { + let auth_key_token = get_auth_key_token(&client).await?; + let (url, certs) = get_kbs_connection(&client).await?; + let ak_der: Vec = ak_secrets + .iter() + .map(|ak| { + let der = ak + .lines() + .filter(|l| !l.starts_with("-----")) + .collect::(); + JsonString(der) + }) + .collect(); + info!("Registering AK to KBS API..."); + kbs_client::set_sample_rv( + url.to_string(), + "trusted_aks".to_string(), + serde_json::Value::Array(ak_der), + Some(auth_key_token), + certs, + ) + .await?; + info!("AK registered successfully"); + Ok(()) +} - let err = format!("Deployment {TRUSTEE_DEPLOYMENT} existed, but had no spec"); - let depl_spec = deployment.spec.as_mut().context(err)?; - let err = format!("Deployment {TRUSTEE_DEPLOYMENT} existed, but had no pod spec"); - let pod_spec = depl_spec.template.spec.as_mut().context(err)?; - let err = format!("Deployment {TRUSTEE_DEPLOYMENT} existed, but had no containers"); - let container = pod_spec.containers.get_mut(0).context(err)?; - let vol_mounts = container.volume_mounts.get_or_insert_default(); +pub async fn sync_all_machine_luks_key(client: Client) -> Result<()> { + let machine_api: Api = Api::default_namespaced(client.clone()); + let machine_list = machine_api.list(&Default::default()).await?; - if add { - let (volume, volume_mount) = generate_secret_volume(id); - pod_spec.volumes.get_or_insert_default().push(volume); - vol_mounts.push(volume_mount); - } else { - let vol_result = pod_spec.volumes.as_mut().and_then(|vs| { - let pos = vs.iter().position(|v| v.name == id); - pos.map(|p| vs.swap_remove(p)) - }); - if vol_result.is_none() { - info!("Secret {id} was to be dropped, but volume had already been removed"); - } - let vol_mount_result = container.volume_mounts.as_mut().and_then(|vms| { - let pos = vms.iter().position(|v| v.name == id); - pos.map(|p| vms.swap_remove(p)) - }); - if vol_mount_result.is_none() { - info!("Secret {id} was to be dropped, but volume mount had already been removed"); - } - } + let machine_ids: Vec = machine_list + .items + .iter() + .map(|machine| machine.spec.id.clone()) + .collect(); - deployments - .replace(TRUSTEE_DEPLOYMENT, &Default::default(), &deployment) - .await?; + info!("Syncing {} machine luks key to KBS", machine_ids.len()); + for id in &machine_ids { + send_secret(client.clone(), id).await?; + } Ok(()) } -pub async fn update_attestation_keys(ctx: &AkContextData) -> Result<()> { - let client = &ctx.client; - let ak_secrets: Vec = ctx - .secret_store - .state() - .into_iter() +pub async fn update_attestation_keys(client: Client) -> Result<()> { + let secrets: Api = Api::default_namespaced(client.clone()); + let secret_list = secrets.list(&Default::default()).await?; + + let ak_secrets: Vec = secret_list + .items + .iter() .filter(|secret| { // Filter out secrets that are being deleted if secret.metadata.deletion_timestamp.is_some() { return false; } - secret .metadata .owner_references @@ -219,125 +425,17 @@ pub async fn update_attestation_keys(ctx: &AkContextData) -> Result<()> { .map(|owners| owners.iter().any(|owner| owner.kind == "AttestationKey")) .unwrap_or(false) }) - .filter_map(|secret| secret.metadata.name.clone()) - .collect(); - - let ns = client.default_namespace().to_string(); - let Some(deployment) = ctx - .deployment_store - .get(&ObjectRef::new(TRUSTEE_DEPLOYMENT).within(&ns)) - .map(std::sync::Arc::unwrap_or_clone) - else { - // Trustee deployment is not (yet or no longer) present — nothing to patch. - info!("{TRUSTEE_DEPLOYMENT} not found in cache, skipping attestation key volume update"); - return Ok(()); - }; - let deployments: Api = Api::default_namespaced(client.clone()); - let err = format!("Deployment {TRUSTEE_DEPLOYMENT} existed, but had no spec"); - let depl_spec = deployment.spec.as_ref().context(err)?; - let err = format!("Deployment {TRUSTEE_DEPLOYMENT} existed, but had no pod spec"); - let pod_spec = depl_spec.template.spec.as_ref().context(err)?; - - // Get existing volumes and volumeMounts, filtering out the attestation key volume - let mut volumes: Vec = pod_spec - .volumes - .as_ref() - .map(|v| { - v.iter() - .filter(|vol| vol.name != TRUSTED_AK_KEYS_VOLUME) - .cloned() - .collect() - }) - .unwrap_or_default(); - - let err = format!("Deployment {TRUSTEE_DEPLOYMENT} existed, but had no containers"); - let container = pod_spec.containers.first().context(err)?; - let mut vol_mounts: Vec = container - .volume_mounts - .as_ref() - .map(|vm| { - vm.iter() - .filter(|mount| mount.name != TRUSTED_AK_KEYS_VOLUME) - .cloned() - .collect() + .filter_map(|secret| { + secret + .data + .as_ref() + .and_then(|d| d.get("public_key")) + .and_then(|pk| String::from_utf8(pk.0.clone()).ok()) }) - .unwrap_or_default(); - - if ak_secrets.is_empty() { - info!( - "No AttestationKey secrets found, removing projected volume from {TRUSTEE_DEPLOYMENT}" - ); - } else { - // Build the projected volume with all AttestationKey secrets - let projections: Vec = ak_secrets - .iter() - .map(|secret_name| VolumeProjection { - secret: Some(SecretProjection { - name: secret_name.to_string(), - items: Some(vec![KeyToPath { - key: "public_key".to_string(), - path: format!("{secret_name}.pub"), - ..Default::default() - }]), - ..Default::default() - }), - ..Default::default() - }) - .collect(); - - let projected_volume = Volume { - name: TRUSTED_AK_KEYS_VOLUME.to_string(), - projected: Some(ProjectedVolumeSource { - sources: Some(projections), - ..Default::default() - }), - ..Default::default() - }; - - volumes.push(projected_volume); - - vol_mounts.push(VolumeMount { - name: TRUSTED_AK_KEYS_VOLUME.to_string(), - mount_path: TRUSTED_AK_KEYS_DIR.to_string(), - ..Default::default() - }); - } - - // Check if volumes or volumeMounts have changed - let volumes_changed = pod_spec.volumes.as_ref() != Some(&volumes); - let vol_mounts_changed = container.volume_mounts.as_ref() != Some(&vol_mounts); - - if volumes_changed || vol_mounts_changed { - // Patch the deployment with updated volumes and volumeMounts - let patch = json!({ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": TRUSTEE_DEPLOYMENT - }, - "spec": { - "template": { - "spec": { - "volumes": volumes, - "containers": [{ - "name": "kbs", - "volumeMounts": vol_mounts - }] - } - } - } - }); + .collect(); - deployments - .patch( - TRUSTEE_DEPLOYMENT, - &PatchParams::apply("trusted-cluster-operator").force(), - &Patch::Apply(&patch), - ) - .await?; - info!("Successfully patched {TRUSTEE_DEPLOYMENT} with attestation key volumes"); - } else { - info!("No changes to attestation key volumes, skipping deployment update"); + if let Err(e) = register_ak(client.clone(), &ak_secrets).await { + warn!("Failed to register AK to KBS: {e}"); } Ok(()) @@ -364,27 +462,32 @@ pub async fn generate_secret( Ok(()) } -pub async fn generate_attestation_policy( +pub async fn generate_trustee_auth_keys_secret( client: Client, owner_reference: OwnerReference, ) -> Result<()> { - let policy_rego = include_str!("tpm.rego"); + let key_pair = generate_ed25519_key_pair()?; let data = BTreeMap::from([ - ("default_cpu.rego".to_string(), policy_rego.to_string()), - // Must create GPU policy or Trustee will attempt to write one to the read-only mount - ("default_gpu.rego".to_string(), String::new()), + ( + TRUSTEE_AUTH_PRIV_KEY.to_string(), + k8s_openapi::ByteString(key_pair.private_key_pem), + ), + ( + TRUSTEE_AUTH_PUB_KEY.to_string(), + k8s_openapi::ByteString(key_pair.public_key_pem), + ), ]); - let config_map = ConfigMap { + let secret = Secret { metadata: ObjectMeta { - name: Some(ATT_POLICY_MAP.to_string()), + name: Some(TRUSTEE_AUTH_SECRET.to_string()), owner_references: Some(vec![owner_reference]), ..Default::default() }, data: Some(data), ..Default::default() }; - create_or_info_if_exists!(client, ConfigMap, config_map); + create_or_info_if_exists!(client, Secret, secret); Ok(()) } @@ -403,6 +506,9 @@ fn generate_kbs_config(has_certificate: bool) -> Result { let tls_cert = toml::Value::String(format!("{TLS_DIR}/tls.crt")); http_server.insert("certificate".to_string(), tls_cert); } else { + warn!( + "Trustee deployment has no TLS certificate, starting KBS with insecure HTTP (not recommended for production)" + ); http_server.insert("insecure_http".to_string(), toml::Value::Boolean(true)); } @@ -416,13 +522,8 @@ pub async fn generate_trustee_data( ) -> Result<()> { let has_certificate = read_certificate(client.clone(), secret).await?.is_some(); let kbs_config = generate_kbs_config(has_certificate)?; - let policy_rego = include_str!("resource.rego"); - let data = BTreeMap::from([ - ("kbs-config.toml".to_string(), kbs_config), - ("policy.rego".to_string(), policy_rego.to_string()), - (REFERENCE_VALUES_FILE.to_string(), "[]".to_string()), - ]); + let data = BTreeMap::from([("kbs-config.toml".to_string(), kbs_config)]); let config_map = ConfigMap { metadata: ObjectMeta { @@ -437,6 +538,21 @@ pub async fn generate_trustee_data( Ok(()) } +pub async fn generate_rv_data(client: Client, owner_reference: OwnerReference) -> Result<()> { + let data = BTreeMap::from([(REFERENCE_VALUES_FILE.to_string(), "[]".to_string())]); + let config_map = ConfigMap { + metadata: ObjectMeta { + name: Some(TRUSTEE_RV_MAP.to_string()), + owner_references: Some(vec![owner_reference]), + ..Default::default() + }, + data: Some(data), + ..Default::default() + }; + create_or_info_if_exists!(client, ConfigMap, config_map); + Ok(()) +} + pub async fn generate_kbs_service( client: Client, owner_reference: OwnerReference, @@ -470,35 +586,37 @@ pub async fn generate_kbs_service( fn generate_kbs_volume_templates() -> [(&'static str, &'static str, Volume); 3] { [ ( - ATT_POLICY_MAP, - "/opt/trustee/policies/opa", + TRUSTEE_DATA_MAP, + TRUSTEE_DATA_DIR, Volume { config_map: Some(ConfigMapVolumeSource { - name: ATT_POLICY_MAP.to_string(), + name: TRUSTEE_DATA_MAP.to_string(), ..Default::default() }), ..Default::default() }, ), ( - TRUSTEE_DATA_MAP, - TRUSTEE_DATA_DIR, + TRUSTEE_AUTH_SECRET, + TRUSTEE_AUTH_KEY_DIR, Volume { - config_map: Some(ConfigMapVolumeSource { - name: TRUSTEE_DATA_MAP.to_string(), + secret: Some(SecretVolumeSource { + secret_name: Some(TRUSTEE_AUTH_SECRET.to_string()), + items: Some(vec![KeyToPath { + key: TRUSTEE_AUTH_PUB_KEY.to_string(), + path: TRUSTEE_AUTH_PUB_KEY.to_string(), + ..Default::default() + }]), ..Default::default() }), ..Default::default() }, ), ( - "resource-dir", - TRUSTEE_SECRETS_PATH, + TRUSTEE_STORAGE_VOLUME, + TRUSTEE_STORAGE_DIR, Volume { - empty_dir: Some(EmptyDirVolumeSource { - medium: Some("Memory".to_string()), - ..Default::default() - }), + empty_dir: Some(EmptyDirVolumeSource::default()), ..Default::default() }, ), @@ -561,7 +679,10 @@ pub async fn generate_kbs_deployment( image: &str, secret: &Option, ) -> Result<()> { - let selector = Some(BTreeMap::from([("app".to_string(), "kbs".to_string())])); + let selector = Some(BTreeMap::from([( + "app".to_string(), + TRUSTEE_APP_LABEL.to_string(), + )])); let tls_volumes = read_certificate(client.clone(), secret).await?; let pod_spec = generate_kbs_pod_spec(image, tls_volumes); @@ -569,6 +690,7 @@ pub async fn generate_kbs_deployment( let deployment = Deployment { metadata: ObjectMeta { name: Some(TRUSTEE_DEPLOYMENT.to_string()), + labels: selector.clone(), owner_references: Some(vec![owner_reference]), ..Default::default() }, @@ -600,8 +722,8 @@ mod tests { use compute_pcrs_lib::Pcr; use compute_pcrs_lib::tpmevents::TPMEventID; use http::{Method, Request, StatusCode}; + use k8s_openapi::apimachinery::pkg::apis::meta::v1::Time; use k8s_openapi::jiff::Timestamp; - use kube::client::Body; use trusted_cluster_operator_test_utils::constants::*; use trusted_cluster_operator_test_utils::mock_client::*; use trusted_cluster_operator_test_utils::test_error_method; @@ -670,18 +792,24 @@ mod tests { #[tokio::test] async fn test_update_rvs_success() { + let _ = jsonwebtoken_openssl::install_default(); let clos = async |req: Request<_>, ctr| match (ctr, req.method()) { (0, &Method::GET) => { assert!(req.uri().path().contains(PCR_CONFIG_MAP)); Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap()) } (1, &Method::GET) | (2, &Method::PUT) => { - assert!(req.uri().path().contains(TRUSTEE_DATA_MAP)); + assert!(req.uri().path().contains(TRUSTEE_RV_MAP)); Ok(serde_json::to_string(&dummy_trustee_map()).unwrap()) } + (3, &Method::GET) => { + assert!(req.uri().path().contains(TRUSTEE_AUTH_SECRET)); + Ok(serde_json::to_string(&dummy_trustee_auth()).unwrap()) + } + (4, &Method::GET) => Ok(serde_json::to_string(&dummy_cluster()).unwrap()), _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; - count_check!(3, clos, |client| { + count_check!(5, clos, |client| { assert!(update_reference_values(client).await.is_ok()); }); } @@ -698,12 +826,12 @@ mod tests { } #[tokio::test] - async fn test_update_rvs_no_trustee_map() { + async fn test_update_rvs_no_rvs_map() { let clos = async |req: Request<_>, ctr| match (ctr, req.uri().path()) { (0, p) if p.contains(PCR_CONFIG_MAP) => { Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap()) } - (1, p) if p.contains(TRUSTEE_DATA_MAP) => Err(StatusCode::NOT_FOUND), + (1, p) if p.contains(TRUSTEE_RV_MAP) => Err(StatusCode::NOT_FOUND), _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; count_check!(2, clos, |client| { @@ -712,12 +840,12 @@ mod tests { } #[tokio::test] - async fn test_update_rvs_no_trustee_data() { + async fn test_update_rvs_no_rvs_data() { let clos = async |req: Request<_>, ctr| match (ctr, req.uri().path()) { (0, p) if p.contains(PCR_CONFIG_MAP) => { Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap()) } - (1, p) if p.contains(TRUSTEE_DATA_MAP) => { + (1, p) if p.contains(TRUSTEE_RV_MAP) => { Ok(serde_json::to_string(&ConfigMap::default()).unwrap()) } _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), @@ -734,133 +862,21 @@ mod tests { assert_eq!(jwk.key.len(), 32); } - fn dummy_deployment() -> Deployment { - Deployment { - spec: Some(DeploymentSpec { - replicas: Some(1), - template: PodTemplateSpec { - spec: Some(PodSpec { - containers: vec![Container::default()], - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - } - } - - #[tokio::test] - async fn test_mount_secret_success() { - let clos = async |req: Request<_>, ctr| match (ctr, req.method()) { - (0, &Method::GET) | (1, &Method::PUT) => { - Ok(serde_json::to_string(&dummy_deployment()).unwrap()) - } - _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), - }; - count_check!(2, clos, |client| { - assert!(mount_secret(client, "id").await.is_ok()); - }); - } - - #[tokio::test] - async fn test_mount_secret_no_depl() { - let clos = async |_, _| Err(StatusCode::NOT_FOUND); - count_check!(1, clos, |client| { - assert!(mount_secret(client, "id").await.is_err()); - }); - } - - #[tokio::test] - async fn test_mount_secret_no_spec() { - let clos = async |_, _| { - let mut depl = dummy_deployment(); - depl.spec = None; - Ok(serde_json::to_string(&depl).unwrap()) - }; - count_check!(1, clos, |client| { - let err = mount_secret(client, "id").await.err().unwrap(); - assert!(err.to_string().contains("but had no spec")); - }); - } - - #[tokio::test] - async fn test_mount_secret_no_pod_spec() { - let clos = async |_, _| { - let mut depl = dummy_deployment(); - let spec = depl.spec.as_mut().unwrap(); - spec.template.spec = None; - Ok(serde_json::to_string(&depl).unwrap()) - }; - count_check!(1, clos, |client| { - let err = mount_secret(client, "id").await.err().unwrap(); - assert!(err.to_string().contains("but had no pod spec")); - }); - } - - #[tokio::test] - async fn test_mount_secret_no_containers() { - let clos = async |_, _| { - let mut depl = dummy_deployment(); - let spec = depl.spec.as_mut().unwrap(); - let pod_spec = spec.template.spec.as_mut().unwrap(); - pod_spec.containers = vec![]; - Ok(serde_json::to_string(&depl).unwrap()) - }; - count_check!(1, clos, |client| { - let err = mount_secret(client, "id").await.err().unwrap(); - assert!(err.to_string().contains("but had no containers")); - }); - } - - #[tokio::test] - async fn test_unmount_secret() { - let clos = async |req: Request, ctr| match (ctr, req.method()) { - (0, &Method::GET) => { - let mut depl = dummy_deployment(); - let spec = depl.spec.as_mut().unwrap(); - let pod_spec = spec.template.spec.as_mut().unwrap(); - pod_spec.volumes = Some(vec![Volume { - name: "id".to_string(), - ..Default::default() - }]); - let container = pod_spec.containers.get_mut(0).unwrap(); - container.volume_mounts = Some(vec![VolumeMount { - name: "id".to_string(), - ..Default::default() - }]); - Ok(serde_json::to_string(&depl).unwrap()) - } - (1, &Method::PUT) => { - let bytes = req.into_body().collect_bytes().await.unwrap().to_vec(); - let body = String::from_utf8_lossy(&bytes); - assert!(!body.contains("id")); - Ok(serde_json::to_string(&dummy_deployment()).unwrap()) - } - _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), - }; - count_check!(2, clos, |client| { - assert!(unmount_secret(client, "id").await.is_ok()); - }); - } - - #[tokio::test] - async fn test_generate_att_policy_success() { - let clos = |client| generate_attestation_policy(client, Default::default()); - test_create_success::<_, _, ConfigMap>(clos).await; - } - - #[tokio::test] - async fn test_generate_att_policy_already_exists() { - let clos = |client| generate_attestation_policy(client, Default::default()); - test_create_already_exists(clos).await; + #[test] + fn test_generate_ed25519_key_pair() { + let pair = generate_ed25519_key_pair().unwrap(); + let priv_pem = String::from_utf8(pair.private_key_pem).unwrap(); + let pub_pem = String::from_utf8(pair.public_key_pem).unwrap(); + assert!(priv_pem.starts_with("-----BEGIN PRIVATE KEY-----")); + assert!(pub_pem.starts_with("-----BEGIN PUBLIC KEY-----")); } - #[tokio::test] - async fn test_generate_att_policy_error() { - let clos = |client| generate_attestation_policy(client, Default::default()); - test_error_method!(clos, Method::POST); + #[test] + fn test_generate_ed25519_key_pair_unique() { + let pair1 = generate_ed25519_key_pair().unwrap(); + let pair2 = generate_ed25519_key_pair().unwrap(); + assert_ne!(pair1.private_key_pem, pair2.private_key_pem); + assert_ne!(pair1.public_key_pem, pair2.public_key_pem); } #[tokio::test] @@ -989,4 +1005,156 @@ mod tests { let vals_pcr7 = reference_values_from(&result, "tpm_pcr7"); assert_eq!(vals_pcr7, vec![PCR7_HASH]); } + #[tokio::test] + async fn test_generate_rv_data_success() { + let clos = |client| generate_rv_data(client, Default::default()); + test_create_success::<_, _, ConfigMap>(clos).await; + } + + #[tokio::test] + async fn test_generate_rv_data_already_exists() { + let clos = |client| generate_rv_data(client, Default::default()); + test_create_already_exists(clos).await; + } + + #[tokio::test] + async fn test_generate_rv_data_error() { + let clos = |client| generate_rv_data(client, Default::default()); + test_error_method!(clos, Method::POST); + } + + #[test] + fn test_recompute_reference_values_includes_svn() { + let result = recompute_reference_values(dummy_pcrs()); + let svn = result.iter().find(|rv| rv.name == "tpm_svn").unwrap(); + let vals = svn.value.as_array().unwrap(); + assert_eq!(vals.len(), 1); + assert_eq!(vals[0].as_str().unwrap(), "1"); + } + + #[test] + fn test_recompute_reference_values_version() { + let result = recompute_reference_values(dummy_pcrs()); + for rv in &result { + assert_eq!(rv.version, "0.1.0"); + } + } + + #[tokio::test] + async fn test_sync_all_machine_luks_key_empty() { + let clos = async |req: Request<_>, ctr| match (ctr, req.method()) { + (0, &Method::GET) => { + let list = kube::api::ObjectList { + items: Vec::::new(), + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&list).unwrap()) + } + _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), + }; + count_check!(1, clos, |client| { + assert!(sync_all_machine_luks_key(client).await.is_ok()); + }); + } + + #[tokio::test] + async fn test_sync_all_machine_luks_key_send_error() { + let clos = async |req: Request<_>, ctr| match (ctr, req.method()) { + (0, &Method::GET) => { + let list = kube::api::ObjectList { + items: vec![dummy_machine("m1")], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&list).unwrap()) + } + (_, &Method::GET) => Err(StatusCode::NOT_FOUND), + _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), + }; + count_check!(2, clos, |client| { + assert!(sync_all_machine_luks_key(client).await.is_err()); + }); + } + + #[tokio::test] + async fn test_update_attestation_keys_empty() { + let clos = async |_, ctr| match ctr { + 0 => { + let list = kube::api::ObjectList { + items: Vec::::new(), + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&list).unwrap()) + } + _ => Err(StatusCode::NOT_FOUND), + }; + count_check!(2, clos, |client| { + assert!(update_attestation_keys(client).await.is_ok()); + }); + } + + #[tokio::test] + async fn test_update_attestation_keys_register_fails_gracefully() { + let clos = async |_, ctr| match ctr { + 0 => { + let list = kube::api::ObjectList { + items: vec![dummy_ak_secret("ak1"), dummy_ak_secret("ak2")], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&list).unwrap()) + } + _ => Err(StatusCode::NOT_FOUND), + }; + count_check!(2, clos, |client| { + assert!(update_attestation_keys(client).await.is_ok()); + }); + } + + #[tokio::test] + async fn test_update_attestation_keys_register_success() { + let _ = jsonwebtoken_openssl::install_default(); + let clos = async |req: Request<_>, ctr| match (ctr, req.method()) { + (0, &Method::GET) => { + let list = kube::api::ObjectList { + items: vec![dummy_ak_secret("ak1")], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&list).unwrap()) + } + (1, &Method::GET) => { + assert!(req.uri().path().contains(TRUSTEE_AUTH_SECRET)); + Ok(serde_json::to_string(&dummy_trustee_auth()).unwrap()) + } + (2, &Method::GET) => Ok(serde_json::to_string(&dummy_cluster()).unwrap()), + _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), + }; + count_check!(3, clos, |client| { + assert!(update_attestation_keys(client).await.is_ok()); + }); + } + + #[tokio::test] + async fn test_update_attestation_keys_filters_deleting() { + let clos = async |_, ctr| match ctr { + 0 => { + let mut deleting = dummy_ak_secret("ak-deleting"); + deleting.metadata.deletion_timestamp = + Some(Time(k8s_openapi::jiff::Timestamp::now())); + let list = kube::api::ObjectList { + items: vec![deleting], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&list).unwrap()) + } + _ => Err(StatusCode::NOT_FOUND), + }; + count_check!(2, clos, |client| { + assert!(update_attestation_keys(client).await.is_ok()); + }); + } } diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 70fa5797..39de2d9e 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -7,7 +7,7 @@ use anyhow::{Context, Result, anyhow}; use constants::APPROVED_IMAGE_NAME; use fs_extra::dir; use glob::glob; -use k8s_openapi::api::apps::v1::{Deployment, DeploymentCondition, DeploymentStatus}; +use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::{ ConfigMap, LoadBalancerStatus, Namespace, Secret, Service, ServicePort, ServiceSpec, ServiceStatus, @@ -637,6 +637,35 @@ impl TestContext { Ok(()) } + pub async fn wait_for_deployment_ready( + &self, + deployments_api: &Api, + deployment_name: &str, + timeout_secs: u64, + ) -> Result<()> { + test_info!( + &self.test_name, + "Waiting for deployment {} to be ready", + deployment_name + ); + let has_available_replica = |d: Option<&Deployment>| { + d.and_then(|d| d.status.as_ref()) + .and_then(|s| s.available_replicas) + .is_some_and(|r| r >= 1) + }; + let done = await_condition( + deployments_api.clone(), + deployment_name, + has_available_replica, + ); + timeout(Duration::from_secs(timeout_secs), done) + .await + .context(format!( + "{deployment_name} deployment does not have 1 available replica after {timeout_secs} seconds" + ))??; + Ok(()) + } + async fn create_certificate( &self, service_name: &str, @@ -982,14 +1011,6 @@ impl TestContext { ); } - let depl_ready = |depl: Option<&Deployment>| { - let chk_cond = |c: &DeploymentCondition| c.type_ == "Available" && c.status == "True"; - let chk_status = - |st: &DeploymentStatus| st.conditions.as_ref().map(|cs| cs.iter().any(chk_cond)); - let chk = |depl: &Deployment| depl.status.as_ref().and_then(chk_status); - depl.and_then(chk).unwrap_or(false) - }; - let depls: Api = Api::namespaced(self.client.clone(), ns); for depl in [ "trusted-cluster-operator", @@ -997,10 +1018,8 @@ impl TestContext { TRUSTEE_DEPLOYMENT, ATTESTATION_KEY_REGISTER_DEPLOYMENT, ] { - self.info(format!("Waiting for deployment {depl} to be ready")); - let done = await_condition(depls.clone(), depl, depl_ready); - let ctx = format!("waiting for deployment {depl} to be ready"); - timeout(scaled_duration(300), done).await.context(ctx)??; + self.wait_for_deployment_ready(&depls, depl, scaled_timeout(300)) + .await?; } let svc = ATTESTATION_KEY_REGISTER_SERVICE; diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 76117005..9518e51b 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -15,6 +15,7 @@ virtualization = [] [dependencies] anyhow.workspace = true cfg-if = "1.0.4" +chrono.workspace = true compute-pcrs-lib.workspace = true hex.workspace = true k8s-openapi.workspace = true diff --git a/tests/no_disallowed_crypto.rs b/tests/no_disallowed_crypto.rs index f1754db7..5c0fcc21 100644 --- a/tests/no_disallowed_crypto.rs +++ b/tests/no_disallowed_crypto.rs @@ -58,6 +58,42 @@ const ALLOWED_CRYPTO_CRATES: &[&str] = &[ "spki", "subtle", "zeroize", + // Pulled in by kbs-client (via kbs_protocol / jwt-simple / cose-rust). + // kbs-client uses reqwest with native-tls (OpenSSL) for actual TLS. + // These crates handle JWS/JWE/COSE token processing, not network TLS. + "aead", + "aes", + "aes-gcm", + "aes-keywrap", + "aes-kw", + "aws-lc-rs", + "chacha20", + "cipher", + "cmov", + "concat-kdf", + "constant_time_eq", + "cose-rust", + "ct-codecs", + "ctr", + "ctutils", + "ed25519-compact", + "ghash", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "jwt-simple", + "k256", + "keccak", + "ml-dsa", + "module-lattice", + "p521", + "polyval", + "sha3", + "siphasher", + "sm3", + "superboring", + "universal-hash", + "zeroize_derive", ]; // Target we actually build and ship for; keeps platform-only crates. diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 62584f58..f4cfd6d7 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -4,14 +4,18 @@ // SPDX-License-Identifier: MIT use anyhow::Context; + +use chrono::Utc; use compute_pcrs_lib::Pcr; use compute_pcrs_lib::tpmevents::{TPMEvent, TPMEventID}; use k8s_openapi::api::apps::v1::Deployment; -use k8s_openapi::api::core::v1::{ConfigMap, Secret}; +use k8s_openapi::api::core::v1::{ConfigMap, Pod, Secret}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; use k8s_openapi::apimachinery::pkg::apis::meta::v1::{Condition, OwnerReference}; -use kube::api::ObjectMeta; +use kube::api::{ListParams, LogParams, Patch, PatchParams}; use kube::runtime::wait::await_condition; use kube::{Api, api::DeleteParams}; +use serde_json::json; use std::time::Duration; use tokio::time::timeout; use trusted_cluster_operator_lib::conditions::NOT_COMMITTED_REASON_PENDING; @@ -21,6 +25,7 @@ use trusted_cluster_operator_lib::{ }; use trusted_cluster_operator_test_utils::constants::*; use trusted_cluster_operator_test_utils::*; +const TRUSTEE_RV_MAP: &str = "trustee-rv-data"; fn ak_approved(ak: Option<&AttestationKey>) -> bool { let is_approved = |c: &Condition| c.type_ == "Approved" && c.status == "True"; @@ -146,8 +151,8 @@ async fn test_image_disallow() -> anyhow::Result<()> { let json = data.and_then(|data| data.get(RV_JSON_KEY)); json.map(|json| !json.contains(PRIMARY_PCR4_HASH)).unwrap_or(false) }; - let rv_removed = await_condition(configmap_api, TRUSTEE_CONFIG_MAP, chk_removed); - let ctx = format!("waiting for ConfigMap {TRUSTEE_CONFIG_MAP} to not contain PCR value"); + let rv_removed = await_condition(configmap_api, TRUSTEE_RV_MAP, chk_removed); + let ctx = format!("waiting for ConfigMap {TRUSTEE_RV_MAP} to not contain PCR value"); timeout(scaled_duration(180), rv_removed).await.context(ctx)??; test_ctx.cleanup().await?; @@ -284,6 +289,392 @@ async fn test_nonexistent_approved_image() -> anyhow::Result<()> { let ctx = "waiting for ApprovedImage coreos1 to be PodPending"; timeout(scaled_duration(30), done).await.context(ctx)??; + test_ctx.cleanup().await?; + Ok(()) +} +} + +named_test! { +async fn test_luks_key_sync() -> anyhow::Result<()> { + let test_ctx = setup!().await?; + let client = test_ctx.client(); + let namespace = test_ctx.namespace(); + let tec_name = "trusted-execution-cluster"; + + let tec_api: Api = Api::namespaced(client.clone(), namespace); + let tec = tec_api.get(tec_name).await?; + let owner_reference = generate_owner_reference(&tec)?; + + // Create two machines + let machine1_uuid = uuid::Uuid::new_v4().to_string(); + let machine1_name = format!("test-machine-{}", &machine1_uuid[..8]); + let machine2_uuid = uuid::Uuid::new_v4().to_string(); + let machine2_name = format!("test-machine-{}", &machine2_uuid[..8]); + + let machines: Api = Api::namespaced(client.clone(), namespace); + + let machine1 = Machine { + metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta { + name: Some(machine1_name.clone()), + namespace: Some(namespace.to_string()), + owner_references: Some(vec![owner_reference.clone()]), + ..Default::default() + }, + spec: trusted_cluster_operator_lib::MachineSpec { + id: machine1_uuid.clone(), + }, + status: None, + }; + machines.create(&Default::default(), &machine1).await?; + test_ctx.info(format!("Created Machine 1: {machine1_name}")); + + let machine2 = Machine { + metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta { + name: Some(machine2_name.clone()), + namespace: Some(namespace.to_string()), + owner_references: Some(vec![owner_reference.clone()]), + ..Default::default() + }, + spec: trusted_cluster_operator_lib::MachineSpec { + id: machine2_uuid.clone(), + }, + status: None, + }; + machines.create(&Default::default(), &machine2).await?; + test_ctx.info(format!("Created Machine 2: {machine2_name}")); + + // Wait for both K8s secrets to be created by the keygen controller + let secrets_api: Api = Api::namespaced(client.clone(), namespace); + wait_for_resource_created(&secrets_api, &machine1_uuid, scaled_timeout(60)).await?; + wait_for_resource_created(&secrets_api, &machine2_uuid, scaled_timeout(60)).await?; + test_ctx.info("Both machine secrets created"); + + // Wait for the operator to send both secrets to the KBS + let pods_api: Api = Api::namespaced(client.clone(), namespace); + let poller = Poller::new() + .with_timeout(scaled_duration(60)) + .with_interval(scaled_duration(2)) + .with_error_message("Secrets not sent to KBS".to_string()); + + poller + .poll_async(|| { + let api = pods_api.clone(); + let id1 = machine1_uuid.clone(); + let id2 = machine2_uuid.clone(); + async move { + let lp = ListParams::default().labels("app=trusted-cluster-operator"); + let operator_pods = api.list(&lp).await?; + let pod_name = operator_pods + .items + .first() + .and_then(|p| p.metadata.name.as_ref()) + .ok_or_else(|| anyhow::anyhow!("Operator pod not found"))? + .clone(); + let logs = api.logs(&pod_name, &LogParams::default()).await?; + if logs.contains(&format!("{id1} sent successfully")) + && logs.contains(&format!("{id2} sent successfully")) + { + return Ok(()); + } + Err(anyhow::anyhow!("Not all secrets sent to KBS yet")) + } + }) + .await?; + test_ctx.info("Both secrets sent to KBS"); + + + let now = Utc::now().to_rfc3339(); + let patch = json!({ + "spec": { + "template": { + "metadata": { + "annotations": { + "kubectl.kubernetes.io/restartedAt": now + } + } + } + } + }); + + test_ctx.info(format!("Triggering rollout restart for deployment: {TRUSTEE_DEPLOYMENT}")); + let deployments: Api = Api::namespaced(client.clone(), namespace); + // Apply the patch + deployments + .patch( + TRUSTEE_DEPLOYMENT, + &PatchParams::default(), + &Patch::Strategic(patch), + ) + .await?; + + test_ctx.wait_for_deployment_ready(&deployments, TRUSTEE_DEPLOYMENT, 120).await?; + + // Wait for the new pod to be ready + test_ctx.info("Trustee deployment is ready after restart"); + + // Verify both secrets are re-synced to KBS after the trustee restart + let poller = Poller::new() + .with_timeout(scaled_duration(60)) + .with_interval(scaled_duration(2)) + .with_error_message("Secrets not re-synced to KBS after restart".to_string()); + + poller + .poll_async(|| { + let api = pods_api.clone(); + let id1 = machine1_uuid.clone(); + let id2 = machine2_uuid.clone(); + async move { + let lp = ListParams::default().labels("app=trusted-cluster-operator"); + let operator_pods = api.list(&lp).await?; + let pod_name = operator_pods + .items + .first() + .and_then(|p| p.metadata.name.as_ref()) + .ok_or_else(|| anyhow::anyhow!("Operator pod not found"))? + .clone(); + let logs = api.logs(&pod_name, &LogParams::default()).await?; + if logs.contains("Syncing 2 machine luks key to KBS") + && logs.matches(&format!("{id1} sent successfully")).count() >= 2 + && logs.matches(&format!("{id2} sent successfully")).count() >= 2 + { + return Ok(()); + } + Err(anyhow::anyhow!("Secrets not yet re-synced to KBS after restart.")) + } + }) + .await?; + test_ctx.info("Both secrets re-synced to KBS after trustee restart"); + + // Delete machine1 and verify its secret is removed from both K8s and KBS + machines + .delete(&machine1_name, &Default::default()) + .await?; + test_ctx.info(format!("Deleted Machine 1: {machine1_name}")); + + let poller = Poller::new() + .with_timeout(scaled_duration(60)) + .with_interval(scaled_duration(2)) + .with_error_message("Machine1 secret not deleted from KBS".to_string()); + + poller + .poll_async(|| { + let api = pods_api.clone(); + let id1 = machine1_uuid.clone(); + async move { + let lp = ListParams::default().labels("app=trusted-cluster-operator"); + let operator_pods = api.list(&lp).await?; + let pod_name = operator_pods + .items + .first() + .and_then(|p| p.metadata.name.as_ref()) + .ok_or_else(|| anyhow::anyhow!("Operator pod not found"))? + .clone(); + let logs = api.logs(&pod_name, &LogParams::default()).await?; + if logs.contains(&format!("Secret {id1} deleted successfully")) { + return Ok(()); + } + Err(anyhow::anyhow!("Machine1 secret not yet deleted from KBS")) + } + }) + .await?; + test_ctx.info("Machine1 secret deleted from KBS"); + + // Verify the K8s Secret for machine1 is also deleted + wait_for_resource_deleted(&secrets_api, &machine1_uuid, 60).await?; + test_ctx.info("Machine1 K8s secret deleted"); + + test_ctx.cleanup().await?; + Ok(()) +} +} + +named_test! { +async fn test_attestation_key_sync() -> anyhow::Result<()> { + let test_ctx = setup!().await?; + let client = test_ctx.client(); + let namespace = test_ctx.namespace(); + let tec_name = "trusted-execution-cluster"; + + let tec_api: Api = Api::namespaced(client.clone(), namespace); + let tec = tec_api.get(tec_name).await?; + let owner_reference = generate_owner_reference(&tec)?; + + // Create two machines + let machine1_uuid = uuid::Uuid::new_v4().to_string(); + let machine1_name = format!("test-machine-{}", &machine1_uuid[..8]); + let machine2_uuid = uuid::Uuid::new_v4().to_string(); + let machine2_name = format!("test-machine-{}", &machine2_uuid[..8]); + + let machines: Api = Api::namespaced(client.clone(), namespace); + let machine1 = Machine { + metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta { + name: Some(machine1_name.clone()), + namespace: Some(namespace.to_string()), + owner_references: Some(vec![owner_reference.clone()]), + ..Default::default() + }, + spec: trusted_cluster_operator_lib::MachineSpec { + id: machine1_uuid.clone(), + }, + status: None, + }; + machines.create(&Default::default(), &machine1).await?; + test_ctx.info(format!("Created Machine 1: {machine1_name}")); + + let machine2 = Machine { + metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta { + name: Some(machine2_name.clone()), + namespace: Some(namespace.to_string()), + owner_references: Some(vec![owner_reference.clone()]), + ..Default::default() + }, + spec: trusted_cluster_operator_lib::MachineSpec { + id: machine2_uuid.clone(), + }, + status: None, + }; + machines.create(&Default::default(), &machine2).await?; + test_ctx.info(format!("Created Machine 2: {machine2_name}")); + + // Create two AttestationKeys with matching UUIDs + let ak1_name = format!("test-ak-{}", &machine1_uuid[..8]); + let ak1_public_key = uuid::Uuid::new_v4().to_string(); + let ak2_name = format!("test-ak-{}", &machine2_uuid[..8]); + let ak2_public_key = uuid::Uuid::new_v4().to_string(); + + let attestation_keys: Api = Api::namespaced(client.clone(), namespace); + + let ak1 = AttestationKey { + metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta { + name: Some(ak1_name.clone()), + namespace: Some(namespace.to_string()), + owner_references: Some(vec![owner_reference.clone()]), + ..Default::default() + }, + spec: trusted_cluster_operator_lib::AttestationKeySpec { + public_key: ak1_public_key, + uuid: Some(machine1_uuid.clone()), + }, + status: None, + }; + attestation_keys.create(&Default::default(), &ak1).await?; + test_ctx.info(format!("Created AttestationKey 1: {ak1_name}")); + + let ak2 = AttestationKey { + metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta { + name: Some(ak2_name.clone()), + namespace: Some(namespace.to_string()), + owner_references: Some(vec![owner_reference.clone()]), + ..Default::default() + }, + spec: trusted_cluster_operator_lib::AttestationKeySpec { + public_key: ak2_public_key, + uuid: Some(machine2_uuid.clone()), + }, + status: None, + }; + attestation_keys.create(&Default::default(), &ak2).await?; + test_ctx.info(format!("Created AttestationKey 2: {ak2_name}")); + + // Wait for both AKs to be approved and have secrets created + let secrets_api: Api = Api::namespaced(client.clone(), namespace); + let ak1_done = await_condition(attestation_keys.clone(), &ak1_name, ak_approved); + let ak2_done = await_condition(attestation_keys.clone(), &ak2_name, ak_approved); + wait_for_resource_created(&secrets_api, &ak1_name, scaled_timeout(60)).await?; + wait_for_resource_created(&secrets_api, &ak2_name, scaled_timeout(60)).await?; + timeout(scaled_duration(60), async { + tokio::try_join!(ak1_done, ak2_done) + }) + .await + .context("waiting for AttestationKeys to be approved with secrets")??; + test_ctx.info("Both AttestationKeys approved and secrets created"); + + // Wait for both AKs to be registered with KBS + let pods_api: Api = Api::namespaced(client.clone(), namespace); + let poller = Poller::new() + .with_timeout(scaled_duration(60)) + .with_interval(scaled_duration(2)) + .with_error_message("AKs not registered with KBS".to_string()); + + poller + .poll_async(|| { + let api = pods_api.clone(); + async move { + let lp = ListParams::default().labels("app=trusted-cluster-operator"); + let operator_pods = api.list(&lp).await?; + let pod_name = operator_pods + .items + .first() + .and_then(|p| p.metadata.name.as_ref()) + .ok_or_else(|| anyhow::anyhow!("Operator pod not found"))? + .clone(); + let logs = api.logs(&pod_name, &LogParams::default()).await?; + let count = logs.matches("AK registered successfully").count(); + if count >= 1 { + return Ok(()); + } + Err(anyhow::anyhow!("Only {count} AK registrations found, need at least 1")) + } + }) + .await?; + test_ctx.info("Both AKs registered with KBS"); + + // Restart the trustee deployment + let now = Utc::now().to_rfc3339(); + let patch = json!({ + "spec": { + "template": { + "metadata": { + "annotations": { + "kubectl.kubernetes.io/restartedAt": now + } + } + } + } + }); + + test_ctx.info(format!("Triggering rollout restart for deployment: {TRUSTEE_DEPLOYMENT}")); + let deployments: Api = Api::namespaced(client.clone(), namespace); + deployments + .patch( + TRUSTEE_DEPLOYMENT, + &PatchParams::default(), + &Patch::Strategic(patch), + ) + .await?; + + test_ctx.wait_for_deployment_ready(&deployments, TRUSTEE_DEPLOYMENT, 120).await?; + test_ctx.info("Trustee deployment is ready after restart"); + + // Verify both AKs are re-registered to KBS after the trustee restart + let poller = Poller::new() + .with_timeout(scaled_duration(60)) + .with_interval(scaled_duration(2)) + .with_error_message("AKs not re-registered with KBS after restart".to_string()); + + poller + .poll_async(|| { + let api = pods_api.clone(); + async move { + let lp = ListParams::default().labels("app=trusted-cluster-operator"); + let operator_pods = api.list(&lp).await?; + let pod_name = operator_pods + .items + .first() + .and_then(|p| p.metadata.name.as_ref()) + .ok_or_else(|| anyhow::anyhow!("Operator pod not found"))? + .clone(); + let logs = api.logs(&pod_name, &LogParams::default()).await?; + let count = logs.matches("AK registered successfully").count(); + if count >= 2 { + return Ok(()); + } + Err(anyhow::anyhow!("Only {count} AK registrations after restart, need at least 2")) + } + }) + .await?; + test_ctx.info("Both AKs re-registered with KBS after trustee restart"); + test_ctx.cleanup().await?; Ok(()) } @@ -312,9 +703,9 @@ async fn test_approved_image_readoption() -> anyhow::Result<()> { test_ctx.info(format!("Deleting TrustedExecutionCluster {TEC_NAME}")); clusters.delete(TEC_NAME, &Default::default()).await?; - wait_for_resource_deleted(&configmaps, TRUSTEE_CONFIG_MAP, scaled_timeout(60)).await?; + wait_for_resource_deleted(&configmaps, TRUSTEE_RV_MAP, scaled_timeout(60)).await?; wait_for_resource_deleted(&images, APPROVED_IMAGE_NAME, scaled_timeout(60)).await?; - test_ctx.info(format!("Configmap {TRUSTEE_CONFIG_MAP} was removed")); + test_ctx.info(format!("Configmap {TRUSTEE_RV_MAP} was removed")); let image = ApprovedImage { spec: image_spec, @@ -343,8 +734,8 @@ async fn test_approved_image_readoption() -> anyhow::Result<()> { let json = data.and_then(|data| data.get(RV_JSON_KEY)); json.map(|json| json.contains(PRIMARY_PCR4_HASH)).unwrap_or(false) }; - let rv_added = await_condition(configmaps, TRUSTEE_CONFIG_MAP, chk_added); - let ctx = format!("waiting for ConfigMap {TRUSTEE_CONFIG_MAP} to contain PCR value"); + let rv_added = await_condition(configmaps, TRUSTEE_RV_MAP, chk_added); + let ctx = format!("waiting for ConfigMap {TRUSTEE_RV_MAP} to contain PCR value"); timeout(scaled_duration(180), rv_added).await.context(ctx)??; test_ctx.info("Reference values regenerated"); @@ -387,7 +778,7 @@ async fn test_combined_image_pcrs_configmap_updates() -> anyhow::Result<()> { } true }; - let done = await_condition(configmaps, "trustee-data", all_expected_pcrs); + let done = await_condition(configmaps, TRUSTEE_RV_MAP, all_expected_pcrs); let ctx = "waiting for ConfigMap trustee-data to contain all expected pcrs"; timeout(scaled_duration(180), done).await.context(ctx)??;