diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee95cc6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +.opencode +.envrc diff --git a/Cargo.lock b/Cargo.lock index 1cdd132..6aaf74c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,15 +8,15 @@ version = "0.6.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b657e772794c6b04730ea897b66a058ccd866c16d1967da05eeeecec39043fe" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout", ] [[package]] name = "aes" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" dependencies = [ "cipher", "cpubits", @@ -26,9 +26,9 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.11.0-rc.3" +version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22c0c90bbe8d4f77c3ca9ddabe41a1f8382d6fc1f7cea89459d0f320371f972" +checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8" dependencies = [ "aead", "aes", @@ -67,6 +67,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -122,17 +131,6 @@ dependencies = [ "syn", ] -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -141,9 +139,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-fips-sys" @@ -281,7 +279,7 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", + "shlex 1.3.0", "syn", ] @@ -303,6 +301,7 @@ dependencies = [ "cfg-if", "constant_time_eq", "cpufeatures 0.3.0", + "serde", "zeroize", ] @@ -326,10 +325,19 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.20.2" +name = "bs58" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" @@ -339,14 +347,14 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.62" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -364,12 +372,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chacha20" version = "0.10.0" @@ -384,13 +386,25 @@ dependencies = [ ] [[package]] -name = "cipher" -version = "0.5.1" +name = "chrono" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout", "zeroize", ] @@ -417,9 +431,9 @@ dependencies = [ [[package]] name = "cmov" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "concurrent-queue" @@ -516,18 +530,18 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] [[package]] name = "ctr" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17469f8eb9bdbfad10f71f4cfddfd38b01143520c0e717d8796ccb4d44d44e42" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ "cipher", ] @@ -543,12 +557,12 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-pre.6" +version = "5.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335f1947f241137a14106b6f5acc5918a5ede29c9d71d3f2cb1678d5075d9fc3" +checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures 0.3.0", "curve25519-dalek-derive", "digest 0.11.3", "fiat-crypto", @@ -601,6 +615,16 @@ dependencies = [ "syn", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -648,18 +672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", - "crypto-common 0.2.1", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "crypto-common 0.2.2", ] [[package]] @@ -691,9 +704,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "3.0.0-pre.7" +version = "3.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20449acd54b660981ae5caa2bcb56d1fe7f25f2e37a38ec507400fab034d4bb6" +checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60" dependencies = [ "curve25519-dalek", "ed25519", @@ -715,9 +728,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "enum-ordinalize" @@ -794,12 +807,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" @@ -931,10 +938,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -944,11 +949,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] @@ -993,23 +996,10 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.4.14" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" @@ -1043,6 +1033,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hostname" version = "0.4.2" @@ -1056,9 +1052,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1111,15 +1107,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", @@ -1167,16 +1162,13 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", - "ipnet", "libc", - "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1185,85 +1177,27 @@ dependencies = [ ] [[package]] -name = "icu_collections" -version = "2.2.0" +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "icu_locale_core" -version = "2.2.0" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", + "cc", ] [[package]] @@ -1279,24 +1213,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "idna" -version = "1.1.0" +name = "indexmap" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", + "autocfg", + "hashbrown 0.12.3", + "serde", ] [[package]] @@ -1320,12 +1244,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - [[package]] name = "itertools" version = "0.13.0" @@ -1352,9 +1270,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1362,14 +1280,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link", ] [[package]] name = "jiff-static" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -1403,9 +1321,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1456,7 +1374,7 @@ checksum = "51b326f5219dd55872a72c1b6ddd1b830b8334996c667449c29391d657d78d5e" dependencies = [ "base64", "jiff", - "schemars", + "schemars 1.2.1", "serde", "serde_json", ] @@ -1521,7 +1439,7 @@ dependencies = [ "jiff", "json-patch", "k8s-openapi", - "schemars", + "schemars 1.2.1", "serde", "serde-value", "serde_json", @@ -1585,20 +1503,21 @@ dependencies = [ "kube", "mimalloc", "opentelemetry", - "opentelemetry-otlp", "opentelemetry_sdk", "prometheus-client", "rand 0.10.1", "rand_hc", - "schemars", + "schemars 1.2.1", "scopeguard", "serde", "serde_json", + "serde_with", "serde_yaml", "smallvec", "ssh-key", "thiserror", "tokio", + "tokio-util", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -1635,19 +1554,13 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.47" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" dependencies = [ "cc", ] -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - [[package]] name = "lock_api" version = "0.4.14" @@ -1659,15 +1572,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "matchers" @@ -1686,15 +1593,15 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mimalloc" -version = "0.1.50" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" dependencies = [ "libmimalloc-sys", ] @@ -1713,9 +1620,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -1741,6 +1648,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-traits" version = "0.2.19" @@ -1764,9 +1677,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "opentelemetry" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" dependencies = [ "futures-core", "futures-sink", @@ -1776,66 +1689,20 @@ dependencies = [ "tracing", ] -[[package]] -name = "opentelemetry-http" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" -dependencies = [ - "async-trait", - "bytes", - "http", - "opentelemetry", - "reqwest", -] - -[[package]] -name = "opentelemetry-otlp" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" -dependencies = [ - "http", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", - "reqwest", - "thiserror", - "tokio", - "tonic", - "tracing", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" -dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost", - "tonic", - "tonic-prost", -] - [[package]] name = "opentelemetry_sdk" -version = "0.31.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" dependencies = [ "futures-channel", "futures-executor", "futures-util", "opentelemetry", "percent-encoding", + "portable-atomic", "rand 0.9.4", "thiserror", - "tokio", - "tokio-stream", ] [[package]] @@ -2008,13 +1875,10 @@ dependencies = [ ] [[package]] -name = "potential_utf" -version = "0.1.5" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" @@ -2067,84 +1931,6 @@ dependencies = [ "syn", ] -[[package]] -name = "prost" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.4", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - [[package]] name = "quote" version = "1.0.45" @@ -2279,46 +2065,6 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "ring" version = "0.17.14" @@ -2365,9 +2111,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2381,7 +2127,6 @@ version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ - "web-time", "zeroize", ] @@ -2417,6 +2162,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "1.2.1" @@ -2549,9 +2306,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -2583,13 +2340,45 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -2633,6 +2422,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -2663,9 +2458,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2673,9 +2468,9 @@ dependencies = [ [[package]] name = "ssh-cipher" -version = "0.3.0-rc.9" +version = "0.3.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36" +checksum = "68126a396f08c64551b44cdb65865ef1d78fdf16ac02b53f78588227f60466fe" dependencies = [ "aes", "aes-gcm", @@ -2689,9 +2484,9 @@ dependencies = [ [[package]] name = "ssh-encoding" -version = "0.3.0-rc.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abf34aa716da5d5b4c496936d042ea282ab392092cd68a72ef6a8863ff8c96a" +checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e" dependencies = [ "base64ct", "ctutils", @@ -2717,12 +2512,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - [[package]] name = "strsim" version = "0.11.1" @@ -2751,20 +2540,6 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "thiserror" @@ -2796,13 +2571,34 @@ dependencies = [ ] [[package]] -name = "tinystr" -version = "0.8.3" +name = "time" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ - "displaydoc", - "zerovec", + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -2826,7 +2622,6 @@ version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ - "bytes", "libc", "mio", "pin-project-lite", @@ -2857,17 +2652,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -2882,43 +2666,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tonic" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" -dependencies = [ - "async-trait", - "base64", - "bytes", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "sync_wrapper", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-prost" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" -dependencies = [ - "bytes", - "prost", - "tonic", -] - [[package]] name = "tower" version = "0.5.3" @@ -2927,9 +2674,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap", "pin-project-lite", - "slab", "sync_wrapper", "tokio", "tokio-util", @@ -2940,14 +2685,13 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "base64", "bitflags", "bytes", - "futures-util", "http", "http-body", "mime", @@ -2956,7 +2700,6 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "url", ] [[package]] @@ -3017,9 +2760,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" dependencies = [ "js-sys", "opentelemetry", @@ -3057,9 +2800,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -3085,7 +2828,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", ] @@ -3107,24 +2850,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "valuable" version = "0.1.1" @@ -3172,9 +2897,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3183,21 +2908,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3205,9 +2920,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3218,9 +2933,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -3242,7 +2957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -3255,20 +2970,10 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.14.0", "semver", ] -[[package]] -name = "web-sys" -version = "0.3.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web-time" version = "1.1.0" @@ -3279,28 +2984,72 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -3318,31 +3067,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -3351,96 +3083,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -3475,7 +3159,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.14.0", "prettyplease", "syn", "wasm-metadata", @@ -3506,7 +3190,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -3525,7 +3209,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.14.0", "log", "semver", "serde", @@ -3535,76 +3219,26 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zeroize" version = "1.8.2" @@ -3625,39 +3259,6 @@ dependencies = [ "syn", ] -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 2898c1a..a64b8e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,18 +18,16 @@ mimalloc.version = "^0.1" mimalloc.features = ["secure"] tokio.version = "^1" tokio.features = ["macros", "rt-multi-thread"] +tokio-util.version = '^0.7' axum.version = "^0.8" ## Tracing tracing.version = "^0.1" -tracing.features = ["max_level_trace", "release_max_level_info"] +tracing.features = ["max_level_debug", "release_max_level_info"] tracing-subscriber.version = "^0.3" -tracing-opentelemetry.version = "^0.32" -opentelemetry.version = "^0.31" -opentelemetry.features = ["trace"] -opentelemetry-otlp.version = "^0.31" -opentelemetry-otlp.features = ["grpc-tonic", "reqwest-rustls"] -opentelemetry_sdk.version = "^0.31" -opentelemetry_sdk.features = ["rt-tokio"] +tracing-opentelemetry.version = "^0.33" +opentelemetry.version = "^0.32" +opentelemetry_sdk.version = "^0.32" +prometheus-client.version = "^0.24" ## Utils jiff.version = "^0.2" scopeguard.version = "^1" @@ -41,6 +39,8 @@ schemars.version = "^1" base64.version = "^0.22" serde.version = "^1" serde.features = ["derive"] +serde_with.version = "3" +serde_with.features = ["hex", "schemars_1"] serde_json.version = "^1" serde_yaml.version = "^0.9" ## k8s @@ -48,7 +48,6 @@ kube.version = "^3" kube.features = ["derive"] k8s-openapi.version = "^0.27" k8s-openapi.features = ["latest", "schemars"] -prometheus-client.version = "^0.24" ## Codegen thiserror.version = "^2" anyhow.version = "^1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d2df58 --- /dev/null +++ b/README.md @@ -0,0 +1,185 @@ +# K8s secret generator + +> 🚧 Please, if you encountered this repo, note that this is under heavy construction. + +> *AI*: This was also an exploration of opencode features, so there is some amount of AI-generated boilerplate. + +## Features + +### Generate namespaced secrets from specification + +To generate arbitrary secret deploy `RandomSecret` resource: + +```yaml +apiVersion: generator.kuyaseg.spushk.ru/v1alpha1 +kind: RandomSecret +metadata: + name: secret + namespace: default +spec: + # Specify all keys to generate and put into secret + keys: + # Binary key - random byte sequence + - key: binary-key + # Change to regenerate value without altering spec + version: 0 + # Determine secret shape + shape: !Bytes + # Set size of value in bytes + size: 64 + # Password key - guaranteed UTF-8 character sequence with repeat period >= 3 + - key: password-key + version: 0 + shape: !Password + # Set length of password in symbols + length: 24 + # Specify which symbols are allowed to use when generating password + dictionary: "abcdefghijklmnopqrstuvwxyz0123456789_" + # Required fields are `key` and `shape` + - key: default-binary-key + shape: !Bytes + - key: default-password + shape: !Password + # Adjust data, stringData and metadata of generated Secret + template: {} +``` + +#### SSH keys + +To generate SSH key deploy `RandomSsh` resource: + +```yaml +apiVersion: generator.kuyaseg.spushk.ru/v1alpha1 +kind: RandomSsh +metadata: + name: ssh-secret + namespace: default +spec: + # Configure keypair configuration + keypair: + # If changes after last reconcilation, will force regeneration with new keypair + version: 0 + # `ed25519` is default. `rsa` is WIP + algorithm: ed25519 + # optional field to set pubkey comment + comment: mysecret + # Configure SSH private key + identity: + # The key to put generated identity to + key: private-key + # Adjust data, stringData and metadata of generated Secret + template: {} + # Configure SSH public key + pubkey: + # The key to put generated identity to + key: public-key + # Adjust data, stringData and metadata of generated Secret + template: {} +``` + +The following template properties have additional validation: +- `metadata.owner_references`: controller owner is not allowed + +When reconciled, this resource will produce two `v1/Secret` resources in the same namespace: +- `-identity`: private SSH key, which is stored in specified key +- `-pubkey`: public SSH key which is stored in specified key + +Both resources inherit namespace and name (as prefix) from `RandomSsh` resource and are immutable. + +### Deterministic secrets + +> ⚠️ This feature exists primarily for testing purposes, where security is less of a concern then repeatability. +> +> While there are use-cases for predictable secrets in production (e.g. DB credentials in some cases), +> you should consider [ESO](https://external-secrets.io) +> with secure external storage to share secrets between clusters and keep secrets stable. +> +> If the seed secret gets compromised - every past and future generated secret can be easliy computed. + +Kuyaseg is able to provide deterministic source for secret and SSH keypair generation, offering idempotent sequence of secret values. + +With deterministic secrets value remains the same even if specification was deleted and created anew. + +This could be useful for testing, debugging and learning in local environments. + +Deterministic secrets are created with `DeterministicSecret` and `DeterministicSsh` resources, matching `RandomSecret` and `RandomSsh` resource schema: + +```yaml +# Schema matches the Secret from generator.kuyaseg.spushk.ru/v1alpha1 +apiVersion: generator.kuyaseg.spushk.ru/v1alpha1 +kind: DeterministicSecret +metadata: + name: deterministic-secret + namespace: testing +spec: + keys: + - key: debug-password + shape: !Password + template: + stringData: + username: debug-user +``` + +Secret values generated by deterministic generator depend on: +- Global `seed` value set for controller +- `metadata.name` and `metadata.namespace` of `DeterministicSecret` resource +- `spec.keys.*.key` and `spec.key.*.version` for each generated key specified in `DeterministicSecret` resource + +Thus, if seed secret remains stable and DeterministicSecret specification does not change - generated keys remain stable and predictable + +To set the `seed` for the controller, deploy a `v1/Secret` and target the controller to it with `KUYASEG_DETERMINISTIC_SEED` env variable. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: controller-seed + namespace: testing +stringData: + my_seed: aabbcc123 +``` + +`KUYASEG_DETERMINISTIC_SEED=controller-seed/my_seed` + +When the later part `/my_seed` is omitted, controller will expect the `seed` key to contain seed value. + +When the seed *value* changes, all known secrets are reconciled. + +## Feature comparison + +We know there are plenty of decent, mature and well-adopted solutions like the one by [mittwald](https://github.com/mittwald/kubernetes-secret-generator) and even [ESO](https://external-secrets.io) which can also generate secrets. + +Here is a comparison. + +||Kuyaseg|kubernetes-secret-generator|ESO|| +|-|-|-|-|-| +|Generate cryptographically secure namespaced secrets|aws-lc-rs+fips, allows to specify generated **keys per resource**|Cryptographically secure, allows to specify generated **keys per resource**|Cryptographically secure, but generated keys are specified **per generator**|| +|Generate SSH keypairs|Ed25519 and RSA OpenSSH keypairs, private and public keys **stored in separate secrets**|Both secret and public keys are **stored in one secret**|Not supported|| +|Deterministic, reproducible secret generation|Available, Hc128 CSPRNG, optional, based on provided seed and secret specs. Stable|Not supported|Not supported|| +|Target secret template|Respects |[Not supported](https://github.com/mittwald/kubernetes-secret-generator/issues/88)|Allows to specity template in `ExternalSecret`|| + +## Security + +⚠️ This software have never received independent security audit. I am not a security expert. + +--- + +For security issues, please, view the [security policy](https://source.spushk.ru/security) and kindly e-mail +us at [reports@spushk.ru](mailto:reports@spushk.ru?subject=%5BSECURITY%5D%3A%20describe%20the%20issue&body=Thank%20you%20for%20submitting%20security%20report%20%E2%9D%A4%EF%B8%8F%0AWe%20kindly%20ask%20you%20not%20to%20publically%20disclose%20any%20information%20related%20to%20your%20findings%20just%20yet.%0APlease%2C%20fill%20out%20the%20following%20sections%20and%20provide%20any%20additional%20information%20you%27re%20willing%20to%20share.%0A%0A-------------------------------------------------------------------------------%20FINDING%0AWhat%20is%20the%20substance%20of%20an%20issue%3F%0A%0A-------------------------------------------------------------------------------%20AFFECTED%20SOFTWARE%0AWhich%20product%20and%20which%20versions%20is%20affected%3F%0A%0A-------------------------------------------------------------------------------%20REPRODUCTION%0APlease%2C%20share%20PoC%20if%20you%20have%20one%0A%0A-------------------------------------------------------------------------------%20DISCLOSURE%0AWhat%20are%20you%20expectations%20on%20disclosure%3F%0AWe%20kindly%20ask%20you%20not%20to%20share%20any%20information%20publically%0Aat%20least%20for%2090%20days%20since%20this%20report.%0A%0A-------------------------------------------------------------------------------%20CONTACT%0AHow%20do%20we%20contact%20you%3F) + +--- + +### Secret generation + +To produce random secrets (`RandomSecret`, `RandomSshSecret`), operator uses [aws-lc-rs+fips](https://docs.rs/aws-lc-rs/latest/aws_lc_rs/). + +To produce deterministic secrets (`DeterministicSecret`, `DeterministicSsh`), operator uses [Hc128](https://docs.rs/rand_hc/latest/rand_hc/) CSPRNG. + +### Secret persistence + +Random secrets defined by `RandomSecret` and `RandomSsh` are unstable: deletion of generated `Secret` will produce new random value on next reconcilation. + +Deterministic secrets defined by `DeterministicSecret` and `DeterministicSsh` are stable as long as the base `seed` and spec/version remain unchanged. +Deletion of generated `Secret` resources will lead to the same pseudo-random value put into the secret on next reconcilation. + +Best effort is taken to ensure that secrets do not remain in memory longer then needed and/or after crush. diff --git a/kuyaseg-controller/Cargo.toml b/kuyaseg-controller/Cargo.toml index 0e6e153..8affe9a 100644 --- a/kuyaseg-controller/Cargo.toml +++ b/kuyaseg-controller/Cargo.toml @@ -11,13 +11,14 @@ publish.workspace = true [[bin]] doc = false -name = "generate_crds" -path = "src/bin/generate_crds.rs" +name = "crdgen" +path = "src/bin/crdgen.rs" [dependencies] ## Rt mimalloc.workspace = true tokio.workspace = true +tokio-util.workspace = true axum.workspace = true ## Tracing tracing.workspace = true @@ -25,7 +26,6 @@ tracing-subscriber.workspace = true tracing-subscriber.features = ["env-filter"] tracing-opentelemetry.workspace = true opentelemetry.workspace = true -opentelemetry-otlp.workspace = true opentelemetry_sdk.workspace = true prometheus-client.workspace = true ## Utils @@ -38,6 +38,7 @@ itertools.workspace = true schemars.workspace = true base64.workspace = true serde.workspace = true +serde_with.workspace = true serde_json.workspace = true serde_yaml.workspace = true ## k8s @@ -54,4 +55,5 @@ ssh-key.workspace = true ssh-key.features = ["ed25519"] rand_hc.workspace = true blake3.workspace = true +blake3.features = ["serde"] zeroize.workspace = true diff --git a/kuyaseg-controller/src/bin/crdgen.rs b/kuyaseg-controller/src/bin/crdgen.rs new file mode 100644 index 0000000..8045fc9 --- /dev/null +++ b/kuyaseg-controller/src/bin/crdgen.rs @@ -0,0 +1,3 @@ +use kube::CustomResourceExt; + +fn main() {} diff --git a/kuyaseg-controller/src/bin/generate_crds.rs b/kuyaseg-controller/src/bin/generate_crds.rs deleted file mode 100644 index e71fdf5..0000000 --- a/kuyaseg-controller/src/bin/generate_crds.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} \ No newline at end of file diff --git a/kuyaseg-controller/src/generator.rs b/kuyaseg-controller/src/generator.rs index ad6a384..f359164 100644 --- a/kuyaseg-controller/src/generator.rs +++ b/kuyaseg-controller/src/generator.rs @@ -30,10 +30,15 @@ pub fn bytes(rng: &mut G, size: usize) -> Vec { buf } -pub fn str_bytes(rng: &mut G, length: usize, dictionary: &[char]) -> Result, GeneratorErr> { +pub fn str_bytes( + rng: &mut G, + length: usize, + dictionary: impl Iterator, +) -> Result, GeneratorErr> { let mut buf = String::with_capacity(length); - let dictionary = Zeroizing::new(dictionary.iter().unique().cloned().collect::>()); + // TODO: unique may leak dictionary..? + let dictionary = Zeroizing::new(dictionary.unique().collect::>()); let distribution = rand::distr::slice::Choose::new(&dictionary).map_err(|_| GeneratorErr::EmptyDistribution)?; let dict_size = dictionary.len(); @@ -65,12 +70,12 @@ pub fn str_bytes(rng: &mut G, length: usize, dictionary: &[char]) } if buf.len() != length { - Err(GeneratorErr::RngStuck { + return Err(GeneratorErr::RngStuck { reason: format!("Unable to sample {length} characters"), - }) - } else { - Ok(buf.into_bytes()) + }); } + + Ok(buf.into_bytes()) } /// Generate SSH keypair, encoded for OpenSSH @@ -194,11 +199,11 @@ mod test { let mut rng = AwsRng::new(); let dict = dict_alnum(32); - let gen0 = str_bytes(&mut rng, 0, &dict).unwrap(); - let gen1 = str_bytes(&mut rng, 1, &dict).unwrap(); - let gen27 = str_bytes(&mut rng, 27, &dict).unwrap(); - let gen256 = str_bytes(&mut rng, 256, &dict).unwrap(); - assert_ne!(str_bytes(&mut rng, 256, &dict).unwrap(), gen256); + let gen0 = str_bytes(&mut rng, 0, dict.iter().cloned()).unwrap(); + let gen1 = str_bytes(&mut rng, 1, dict.iter().cloned()).unwrap(); + let gen27 = str_bytes(&mut rng, 27, dict.iter().cloned()).unwrap(); + let gen256 = str_bytes(&mut rng, 256, dict.iter().cloned()).unwrap(); + assert_ne!(str_bytes(&mut rng, 256, dict.iter().cloned()).unwrap(), gen256); // length assert_eq!(gen0.len(), 0); @@ -222,7 +227,7 @@ mod test { let mut rng = AwsRng::new(); let dict: Vec = dict_alnum(3); - let gen16 = str_bytes(&mut rng, 16, &dict).unwrap(); + let gen16 = str_bytes(&mut rng, 16, dict.iter().cloned()).unwrap(); assert_eq!(gen16.len(), 16); assert_dict_chars(&gen16, &dict); @@ -235,8 +240,6 @@ mod test { verify_ssh_pair(&pair); } - // ====== Deterministic (Hc128Rng) ====== - #[test] fn deterministic_bytes() { // we believe that generation is the same as for random, so our concern here is only value stability @@ -258,12 +261,12 @@ mod test { let dict = dict_alnum(32); assert_eq!( - str_bytes(&mut Hc128Rng::seed_from_u64(42), 256, &dict).unwrap(), - str_bytes(&mut Hc128Rng::seed_from_u64(42), 256, &dict).unwrap() + str_bytes(&mut Hc128Rng::seed_from_u64(42), 256, dict.iter().cloned()).unwrap(), + str_bytes(&mut Hc128Rng::seed_from_u64(42), 256, dict.iter().cloned()).unwrap() ); assert_ne!( - str_bytes(&mut Hc128Rng::seed_from_u64(42), 256, &dict).unwrap(), - str_bytes(&mut Hc128Rng::seed_from_u64(43), 256, &dict).unwrap() + str_bytes(&mut Hc128Rng::seed_from_u64(42), 256, dict.iter().cloned()).unwrap(), + str_bytes(&mut Hc128Rng::seed_from_u64(43), 256, dict.iter().cloned()).unwrap() ); } diff --git a/kuyaseg-controller/src/lib.rs b/kuyaseg-controller/src/lib.rs index fed3523..c185f23 100644 --- a/kuyaseg-controller/src/lib.rs +++ b/kuyaseg-controller/src/lib.rs @@ -1,5 +1,4 @@ -#![feature(sync_nonpoison, nonpoison_rwlock)] - pub mod generator; pub mod reporting; +pub mod resource; pub mod utils; diff --git a/kuyaseg-controller/src/reporting/k8s_event.rs b/kuyaseg-controller/src/reporting/k8s.rs similarity index 86% rename from kuyaseg-controller/src/reporting/k8s_event.rs rename to kuyaseg-controller/src/reporting/k8s.rs index 5bd1294..8254852 100644 --- a/kuyaseg-controller/src/reporting/k8s_event.rs +++ b/kuyaseg-controller/src/reporting/k8s.rs @@ -25,7 +25,7 @@ impl K8sEventRecorder { .recorder .publish(&event.event, &event.object) .await - .inspect_err(|e| tracing::error!(no_event=true, error=?e, "Failed to record K8s event")); + .inspect_err(|e| tracing::error!(k13t.no_report=true, debug=?e, "Failed to record K8s event")); } } diff --git a/kuyaseg-controller/src/reporting/mod.rs b/kuyaseg-controller/src/reporting/mod.rs index 240a9c4..6132d3f 100644 --- a/kuyaseg-controller/src/reporting/mod.rs +++ b/kuyaseg-controller/src/reporting/mod.rs @@ -4,6 +4,7 @@ use std::time::Duration; use anyhow::Context; use confique::Config; +use opentelemetry::TraceId; use tokio::sync::broadcast::{ self, Sender, @@ -11,19 +12,27 @@ use tokio::sync::broadcast::{ use tokio::task::JoinSet; use tokio_util::sync::CancellationToken; -use crate::reporting::k8s_event::K8sEvent; -use crate::reporting::otel::OtelObservation; +use crate::reporting::k8s::K8sEvent; use crate::reporting::prometheus::ProLabel; use crate::reporting::tracing_evt::{ TracingK8sEventLayer, TracingK8sEventLayerConfig, }; -pub mod k8s_event; -pub mod otel; +pub mod k8s; pub mod prometheus; pub mod tracing_evt; +pub fn span_trace_id() -> Option { + use opentelemetry::trace::TraceContextExt; + use tracing_opentelemetry::OpenTelemetrySpanExt; + + match tracing::Span::current().context().span().span_context().trace_id() { + TraceId::INVALID => None, + other => Some(other), + } +} + #[derive(Config)] pub struct ReportingConfig { #[config(env = "KUYASEG_REPORT_K8S")] @@ -32,24 +41,16 @@ pub struct ReportingConfig { #[config(env = "KUYASEG_REPORT_PROMETHEUS")] #[config(default = false)] pub prometheus: bool, - #[config(env = "KUYASEG_REPORT_OTEL")] - #[config(default = false)] - pub opentelemetry: bool, -} - -pub fn trace_id_str(trace_id: &opentelemetry::TraceId) -> Option { - match trace_id { - &opentelemetry::TraceId::INVALID => None, - x => Some(x.to_string()), - } } /// You should await on handle somewhere pub struct ReportingInit { pub tracing_layer: TracingK8sEventLayer, pub handle: JoinSet<()>, + pub pro_registry: Option, } +/// For opentelemetry TraceId to be present, tracing_opentelemetry::layer must also be set pub fn init(k8s_client: kube::Client, ct: CancellationToken) -> anyhow::Result { let config = ReportingConfig::builder() .env() @@ -61,21 +62,18 @@ pub fn init(k8s_client: kube::Client, ct: CancellationToken) -> anyhow::Result, ct: Cancella Reporter, }; - use crate::reporting::k8s_event::K8sEventRecorder; + use crate::reporting::k8s::K8sEventRecorder; const WARN_THRESHOLD: usize = 10; const MAX_CAPACITY: usize = 100; @@ -126,7 +124,7 @@ fn init_k8s_events(client: kube::Client, joinset: &mut JoinSet<()>, ct: Cancella } queue.push_back(ev); if queue.len() > WARN_THRESHOLD && !warned { - tracing::warn!(no_event=true, queue_len=queue.len(), "K8s event queue exceeded threshold"); + tracing::warn!(k13t.no_report=true, debug=?queue.len(), "K8s event queue exceeded threshold"); warned = true; } else if queue.len() <= WARN_THRESHOLD { warned = false; @@ -135,7 +133,7 @@ fn init_k8s_events(client: kube::Client, joinset: &mut JoinSet<()>, ct: Cancella rec_limit = 4; } Err(e) => { - tracing::error!(no_event=true, debug=?e, "Failed to receive k8s event from tracing"); + tracing::error!(k13t.no_report=true, debug=?e, "Failed to receive k8s event from tracing"); rx = rtx.subscribe(); // dumb recover strategy tokio::time::sleep(Duration::from_secs(3)).await; rec_limit -= 1; @@ -145,7 +143,7 @@ fn init_k8s_events(client: kube::Client, joinset: &mut JoinSet<()>, ct: Cancella } } tracing::error!( - no_event = true, + k13t.no_report = true, "K8s Event reporting from tracing stopped" ); }); @@ -153,7 +151,10 @@ fn init_k8s_events(client: kube::Client, joinset: &mut JoinSet<()>, ct: Cancella tx } -fn init_pro_metrics(joinset: &mut JoinSet<()>, ct: CancellationToken) -> Sender> { +fn init_pro_metrics( + joinset: &mut JoinSet<()>, + ct: CancellationToken, +) -> (Sender>, prometheus_client::registry::Registry) { use prometheus_client::registry::Registry; use crate::reporting::prometheus::ProMetrics; @@ -161,10 +162,7 @@ fn init_pro_metrics(joinset: &mut JoinSet<()>, ct: CancellationToken) -> Sender< let (tx, mut rx) = broadcast::channel::>(512); let metrics = ProMetrics::new(); - let registration_cancellation = CancellationToken::new(); - let rtx = tx.clone(); - let ctk = registration_cancellation.clone(); let promt = metrics.clone(); let consumer_ct = ct.clone(); joinset.spawn(async move { @@ -180,7 +178,7 @@ fn init_pro_metrics(joinset: &mut JoinSet<()>, ct: CancellationToken) -> Sender< rec_limit = 4; } Err(e) => { - tracing::error!(no_pro=true, debug=?e, "Failed to receive prometheus measurement from tracing"); + tracing::error!(k13t.no_report=true, debug=?e, "Failed to receive prometheus measurement from tracing"); rx = rtx.subscribe(); // dumb recover strategy; tokio::time::sleep(Duration::from_secs(3)).await; rec_limit -= 1; @@ -189,82 +187,17 @@ fn init_pro_metrics(joinset: &mut JoinSet<()>, ct: CancellationToken) -> Sender< } } } - ctk.cancel(); tracing::error!( - no_pro = true, + k13t.no_report=true, "Prometheus measurement from tracing stopped" ); }); - let ctk = ct.clone(); - joinset.spawn(async move { + let registry = { let mut registry = Registry::default(); - loop { - tokio::select! { - _ = tokio::time::sleep(Duration::from_secs(10)) => { - metrics.register(&mut registry); - }, - _ = registration_cancellation.cancelled() => { - tracing::warn!(no_pro=true, "Stopped publishing prometheus metrics: consumer job cancelled"); - break; - } - _ = ctk.cancelled() => { - tracing::warn!(no_pro=true, "Stopped publishing prometheus metrics: shutdown"); - break; - } - } - } - }); + metrics.register(&mut registry); + registry + }; - tx -} - -fn init_otel(joinset: &mut JoinSet<()>, ct: CancellationToken) -> anyhow::Result>> { - use crate::reporting::otel::OtelAggregator; - - let exporter = opentelemetry_otlp::LogExporter::builder() - .with_tonic() - .build() - .context("Failed to build OTLP log exporter")?; - - let provider = opentelemetry_sdk::logs::SdkLoggerProvider::builder() - .with_batch_exporter(exporter) - .build(); - - let aggregator = Arc::new(OtelAggregator::new(provider)); - let task_aggregator = aggregator.clone(); - - let (tx, mut rx) = broadcast::channel::>(512); - - let rtx = tx.clone(); - joinset.spawn(async move { - let mut rec_limit = 4; - while rec_limit > 0 { - tokio::select! { - biased; - _ = ct.cancelled() => break, - msg = rx.recv() => { - match msg { - Ok(obs) => { - task_aggregator.observe(&obs); - rec_limit = 4; - } - Err(e) => { - tracing::error!( - no_otel = true, - debug = ?e, - "Failed to receive otel observation from tracing" - ); - rx = rtx.subscribe(); - tokio::time::sleep(Duration::from_secs(3)).await; - rec_limit -= 1; - } - } - } - } - } - tracing::error!(no_otel = true, "OpenTelemetry reporting from tracing stopped"); - }); - - Ok(tx) + (tx, registry) } diff --git a/kuyaseg-controller/src/reporting/otel.rs b/kuyaseg-controller/src/reporting/otel.rs deleted file mode 100644 index f850d4c..0000000 --- a/kuyaseg-controller/src/reporting/otel.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::fmt::Debug; - -use opentelemetry::logs::{ - AnyValue, - LogRecord, - Logger, - LoggerProvider, - Severity, -}; -use opentelemetry_sdk::logs::SdkLoggerProvider; - -pub struct OtelLogFields { - pub severity: Severity, - pub message: String, - pub reason: String, - pub action: String, - pub k8s_name: String, - pub k8s_kind: String, - pub k8s_ns: Option, - pub k8s_api: Option, - pub k8s_uid: Option, - pub trace_id: Option, -} - -pub enum OtelObservation { - Log { fields: OtelLogFields }, - Metric {}, -} - -impl Debug for OtelObservation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let variant = match self { - Self::Log { .. } => "Log", - Self::Metric { .. } => "Metric", - }; - write!(f, "OtelObservation::{}", variant) - } -} - -pub struct OtelAggregator { - provider: SdkLoggerProvider, - logger: ::Logger, -} - -impl OtelAggregator { - pub fn new(provider: SdkLoggerProvider) -> Self { - let logger = provider.logger("kuyaseg-controller"); - Self { provider, logger } - } - - pub fn observe(&self, obs: &OtelObservation) { - match obs { - OtelObservation::Log { fields } => self.observe_log(fields), - OtelObservation::Metric {} => { /*Not implemented */ } - } - } - - pub fn force_flush(&self) { - if let Err(e) = self.provider.force_flush() { - tracing::warn!(error = ?e, "otel export force_flush failed"); - } - } - - fn observe_log(&self, fields: &OtelLogFields) { - let record = { - let mut record = self.logger.create_log_record(); - record.set_severity_number(fields.severity); - record.set_severity_text(fields.severity.name()); - record.set_body(AnyValue::from(fields.message.to_string())); - record.set_target("kuyaseg-controller"); - - record.add_attribute("reason", fields.reason.to_string()); - record.add_attribute("action", fields.action.to_string()); - record.add_attribute("k8s.resource.name", fields.k8s_name.to_string()); - record.add_attribute("k8s.resource.kind", fields.k8s_kind.to_string()); - - if let Some(ns) = fields.k8s_ns.as_ref() { - record.add_attribute("k8s.namespace.name", ns.to_string()); - } - if let Some(api) = fields.k8s_api.as_ref() { - record.add_attribute("k8s.resource.api_version", api.to_string()); - } - if let Some(uid) = fields.k8s_uid.as_ref() { - record.add_attribute("k8s.resource.uid", uid.to_string()); - } - if let Some(trace_id) = fields.trace_id.as_ref() { - record.add_attribute("trace_id", trace_id.to_string()); - } - - record - }; - - self.logger.emit(record); - } - - #[deprecated(note = "Not implemented")] - fn observe_metric(&self, _metric: ()) { - todo!("Metrics are not implemented and should not be used for now") - } -} - -impl Drop for OtelAggregator { - fn drop(&mut self) { - self.force_flush(); - } -} diff --git a/kuyaseg-controller/src/reporting/prometheus.rs b/kuyaseg-controller/src/reporting/prometheus.rs index 4d0d7ba..dfcc3b1 100644 --- a/kuyaseg-controller/src/reporting/prometheus.rs +++ b/kuyaseg-controller/src/reporting/prometheus.rs @@ -4,7 +4,6 @@ use std::time::{ SystemTime, }; -use opentelemetry::trace::TraceContextExt; use prometheus_client::encoding::{ EncodeLabelSet, EncodeLabelValue, @@ -16,9 +15,8 @@ use prometheus_client::metrics::exemplar::{ }; use prometheus_client::metrics::family::Family; use prometheus_client::registry::Registry; -use tracing_opentelemetry::OpenTelemetrySpanExt; -use crate::reporting::trace_id_str; +use crate::reporting::span_trace_id; #[derive(Default, Clone)] pub struct ProMetrics { @@ -47,6 +45,7 @@ impl ProMetrics { } /// Start measuring reconcile duration. Registers on `TimedMeasurer` drop + // TODO: use this pub fn measure_reconcilation(&self) -> TimedMeasurer { self.reconcile_metrics.measure() } @@ -71,8 +70,9 @@ pub struct ProReconcileMetrics { impl ProReconcileMetrics { pub fn measure(&self) -> TimedMeasurer { self.runs.inc(); - let trace_id = tracing::Span::current().context().span().span_context().trace_id(); - let label = trace_id_str(&trace_id).map(|trace_id| TraceLabel { trace_id }); + let label = span_trace_id().map(|id| TraceLabel { + trace_id: id.to_string(), + }); TimedMeasurer { label, diff --git a/kuyaseg-controller/src/reporting/tracing_evt.rs b/kuyaseg-controller/src/reporting/tracing_evt.rs index 0e1c8fa..f94b612 100644 --- a/kuyaseg-controller/src/reporting/tracing_evt.rs +++ b/kuyaseg-controller/src/reporting/tracing_evt.rs @@ -5,7 +5,6 @@ use kube::runtime::events::{ Event, EventType, }; -use opentelemetry::logs::Severity; use tokio::sync::broadcast::Sender; use tracing::field::Visit; use tracing::span::Attributes; @@ -14,31 +13,23 @@ use tracing::{ Id, Subscriber, }; -use tracing_opentelemetry::OtelData; use tracing_subscriber::Layer; use tracing_subscriber::layer::Context; use tracing_subscriber::registry::LookupSpan; -use crate::reporting::k8s_event::K8sEvent; -use crate::reporting::otel::{ - OtelLogFields, - OtelObservation, -}; +use crate::reporting::k8s::K8sEvent; use crate::reporting::prometheus::{ ProK8sExemplar, ProK8sLabel, ProK8sSeverity, ProLabel, }; -use crate::reporting::trace_id_str; pub struct TracingK8sEventLayerConfig { /// Set to enable k8s events from tracing pub k8s_events: Option>>, /// set to enable prometheus metrics from tracing pub prometheus: Option>>, - /// set to enable otel observations from tracing - pub otel: Option>>, } /// Watches for tracing events, containing information necessary to construst k8s events and write prometheus metrics. @@ -46,10 +37,11 @@ pub struct TracingK8sEventLayerConfig { /// - k8s native events you must record yourself - thats what `tx` is for. /// - prometheus metrics are written automatically (if metrics set), but labels are also send with `tx`. /// - k8s events are rate-limited to 10 events/min +/// +/// Recognizes fields with `k13t` (KubernatesEvent) prefix. pub struct TracingK8sEventLayer { k8s_tx: Option>>, pro_tx: Option>>, - otel_tx: Option>>, } impl TracingK8sEventLayer { @@ -57,7 +49,6 @@ impl TracingK8sEventLayer { Self { k8s_tx: config.k8s_events, pro_tx: config.prometheus, - otel_tx: config.otel, } } } @@ -90,6 +81,7 @@ where return; } + let span = ctx.event_span(event); let scope = match ctx.event_scope(event) { Some(s) => s, None => { @@ -122,11 +114,13 @@ where return; } - // gather trace_id - let otel_trace_id = ctx - .event_span(event) - .and_then(|span| span.extensions().get::().and_then(|dat| dat.trace_id())); - let str_trace_id = otel_trace_id.as_ref().and_then(trace_id_str); + let trace_id = span + .and_then(|s| { + s.extensions() + .get::() + .map(|ctx| ctx.trace_id()) + }) + .map(|id| id.to_string()); // write k8s event let k8s_event_type = match *event.metadata().level() { @@ -145,7 +139,7 @@ where object: span_fields.to_object(), event, })) - .inspect_err(|e| tracing::error!(debug=?e, "Failed to issue event on k8s event from tracing")); + .inspect_err(|e| tracing::error!(k13t.no_report=true, debug=?e, "Failed to issue event on k8s event from tracing")); } // write/measure pro event @@ -163,48 +157,13 @@ where }; if event_fields.no_pro.is_none_or(|v| !v) && span_fields.no_pro.is_none_or(|v| !v) - && let Some(label) = try_create_pro_label(&span_fields, &event_fields, pro_severity, str_trace_id.clone()) + && let Some(label) = try_create_pro_label(&span_fields, &event_fields, pro_severity, trace_id) && let Some(tx) = self.pro_tx.as_ref() { _ = tx.send(Arc::new(label)).inspect_err( - |e| tracing::error!(debug=?e, "Failed to issue event on prometheus label measure from tracing"), + |e| tracing::error!(k13t.no_report=true, debug=?e, "Failed to issue event on prometheus label measure from tracing"), ); } - - // write otel event - let otel_severity = match *event.metadata().level() { - tracing::Level::ERROR => { - if event_fields.critical.is_some_and(|v| v) { - Severity::Fatal - } else { - Severity::Error - } - } - tracing::Level::WARN => Severity::Warn, - tracing::Level::INFO => Severity::Info, - _ => return, - }; - if event_fields.no_otel.is_none_or(|v| !v) - && span_fields.no_otel.is_none_or(|v| !v) - && let Some(tx) = self.otel_tx.as_ref() - { - _ = tx - .send(Arc::new(OtelObservation::Log { - fields: OtelLogFields { - severity: otel_severity, - message: event_fields.message.clone().unwrap_or_default(), - reason: event_fields.reason.clone().unwrap_or_default(), - action: event_fields.action.clone().unwrap_or_default(), - k8s_name: span_fields.name.clone().unwrap_or_default(), - k8s_kind: span_fields.kind.clone().unwrap_or_default(), - k8s_ns: span_fields.ns.clone(), - k8s_api: span_fields.api.clone(), - k8s_uid: span_fields.uid.clone(), - trace_id: str_trace_id.clone(), - }, - })) - .inspect_err(|e| tracing::error!(debug=?e, "Failed to issue otel observation from tracing")); - } } } @@ -289,22 +248,22 @@ impl SpanFields { impl Visit for SpanFields { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { - "k8s_name" => self.name = Some(value.to_string()), - "k8s_ns" => self.ns = Some(value.to_string()), - "k8s_kind" => self.kind = Some(value.to_string()), - "k8s_api" => self.api = Some(value.to_string()), - "k8s_uid" => self.uid = Some(value.to_string()), - "k8s_version" => self.version = Some(value.to_string()), - "k8s_field_path" => self.field_path = Some(value.to_string()), + "k13t.name" => self.name = Some(value.to_string()), + "k13t.ns" => self.ns = Some(value.to_string()), + "k13t.kind" => self.kind = Some(value.to_string()), + "k13t.api" => self.api = Some(value.to_string()), + "k13t.uid" => self.uid = Some(value.to_string()), + "k13t.version" => self.version = Some(value.to_string()), + "k13t.field_path" => self.field_path = Some(value.to_string()), _ => {} } } fn record_bool(&mut self, field: &tracing::field::Field, value: bool) { match field.name() { - "no_event" => self.no_event = Some(value), - "no_pro" => self.no_pro = Some(value), - "no_otel" => self.no_otel = Some(value), + "k13t.no_event" => self.no_event = Some(value), + "k13t.no_pro" => self.no_pro = Some(value), + "k13t.no_otel" => self.no_otel = Some(value), _ => {} } } @@ -314,12 +273,14 @@ impl Visit for SpanFields { #[derive(Default, Clone)] struct EventFields { - /// Setting this to true will block entire span from emitting k8s events + /// Setting this to true will prevent from emitting k8s events no_event: Option, - /// Setting this to true will block entire span from emitting prometheus metric labels + /// Setting this to true will prevent from emitting prometheus metric labels no_pro: Option, - /// Setting this to true will block entire span from emitting opentelemetry logs, + /// Setting this to true will prevent from emitting opentelemetry logs, no_otel: Option, + /// Setting this to true will prevent from emitting any pro/otel/k8s events + no_report: Option, /// Only for: /// - prometheus label: force event severity to "critical". Only triggers on `ERROR` tracing level /// - opentelemetry: make event severify to "Fatal". Only triggers on `ERROR` tracing level @@ -332,7 +293,7 @@ struct EventFields { impl EventFields { /// Scope independend custom field validation fn check_fields(&self) -> bool { - self.reason.is_some() && self.action.is_some() + !self.no_report.is_some_and(|v| v) && self.reason.is_some() && self.action.is_some() } /// Return `None` if hard-requirements on fields not met. @@ -351,19 +312,20 @@ impl EventFields { impl Visit for EventFields { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { match field.name() { - "reason" => self.reason = Some(value.to_string()), - "action" => self.action = Some(value.to_string()), - "message" => self.message = Some(value.to_string()), + "k13t.reason" => self.reason = Some(value.to_string()), + "k13t.action" => self.action = Some(value.to_string()), + "k13t.message" => self.message = Some(value.to_string()), _ => {} } } fn record_bool(&mut self, field: &tracing::field::Field, value: bool) { match field.name() { - "critical" => self.critical = Some(value), - "no_event" => self.no_event = Some(value), - "no_pro" => self.no_pro = Some(value), - "no_otel" => self.no_otel = Some(value), + "k13t.critical" => self.critical = Some(value), + "k13t.no_event" => self.no_event = Some(value), + "k13t.no_pro" => self.no_pro = Some(value), + "k13t.no_otel" => self.no_otel = Some(value), + "k13t.no_report" => self.no_report = Some(value), _ => {} } } diff --git a/kuyaseg-controller/src/resource/generated/mod.rs b/kuyaseg-controller/src/resource/generated/mod.rs new file mode 100644 index 0000000..899b535 --- /dev/null +++ b/kuyaseg-controller/src/resource/generated/mod.rs @@ -0,0 +1,2 @@ +pub mod secret; +pub mod ssh; diff --git a/kuyaseg-controller/src/resource/generated/secret/mod.rs b/kuyaseg-controller/src/resource/generated/secret/mod.rs new file mode 100644 index 0000000..32a5a9d --- /dev/null +++ b/kuyaseg-controller/src/resource/generated/secret/mod.rs @@ -0,0 +1 @@ +pub mod v1alpha1; diff --git a/kuyaseg-controller/src/resource/generated/secret/v1alpha1/mod.rs b/kuyaseg-controller/src/resource/generated/secret/v1alpha1/mod.rs new file mode 100644 index 0000000..871bd5b --- /dev/null +++ b/kuyaseg-controller/src/resource/generated/secret/v1alpha1/mod.rs @@ -0,0 +1,354 @@ +use std::collections::{ + BTreeMap, + HashMap, +}; + +use k8s_openapi::ByteString; +use schemars::JsonSchema; +use serde::{ + Deserialize, + Serialize, +}; +use serde_with::serde_as; +use zeroize::Zeroize; + +use crate::utils::{ + serializers, + zeroizers, +}; + +mod pseudorandom; +pub use pseudorandom::*; + +mod random; +pub use random::*; + +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SecretStatus { + pub key_tracker: HashMap, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde_as] +pub struct KeyStatus { + /// Version is tracked separately, for change must set NE status + pub version: usize, + #[schemars(schema_with = "String::json_schema")] + #[serde_as(as = "serde_with::hex::Hex")] + pub spec_hash: blake3::Hash, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq))] +pub struct KeySpec { + /// Name of the key to put generated data into + pub key: String, + /// Tracks the key generation version within the same shape. + /// Increasing this number will force value regeneration on next reconcilation + pub version: usize, + /// Determines the shape of generated data + pub shape: KeyShape, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq))] +pub enum KeyShape { + /// Generate arbitrary bytes + Bytes { + /// Size of generated sequence in bytes. + /// + /// Defaults to 64 + #[serde(default = "KeyShape::default_bytes_size")] + size: usize, + }, + /// Generate string password + Password { + /// The length of password in characters. + /// + /// Defaults to 24 + #[serde(default = "KeyShape::default_pwd_length")] + length: usize, + /// The string of characters allowed to be present in password. + /// When dictionary length is > 4, each character in generated password is guaranteed to repeat + /// with period at least 3. + /// + /// Defaults to lowercase+uppercase alphanumeric and `-` + #[serde(default = "KeyShape::default_pwd_dict")] + dictionary: String, + }, +} + +impl KeyShape { + /// BLAKE3 hash, incorporating shape and according properties + pub fn stable_hash(&self) -> blake3::Hash { + let mut hasher = blake3::Hasher::new(); + match self { + Self::Bytes { size } => { + hasher.update(b"bytes"); + hasher.update(&size.to_le_bytes()); + } + Self::Password { length, dictionary } => { + hasher.update(b"password"); + hasher.update(&length.to_le_bytes()); + hasher.update(dictionary.as_bytes()); + } + } + hasher.finalize() + } + + pub fn default_bytes_size() -> usize { + 64 + } + pub fn default_pwd_length() -> usize { + 24 + } + pub fn default_pwd_dict() -> String { + String::from("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-") + } +} + +impl Zeroize for KeyShape { + #[inline] + fn zeroize(&mut self) { + match self { + Self::Bytes { size } => size.zeroize(), + Self::Password { length, dictionary } => { + length.zeroize(); + dictionary.zeroize(); + } + } + } +} + +impl Drop for KeyShape { + fn drop(&mut self) { + self.zeroize(); + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq))] +pub struct SecretTemplate { + /// Define created Secret resource metadata + #[serde(default)] + pub metadata: SecretMetadata, + /// Include static data into created Secret + #[serde(default)] + #[schemars(schema_with = "BTreeMap::::json_schema")] + pub data: BTreeMap, + /// Include static string data into created Secret + #[serde(default)] + #[serde(rename = "stringData")] + #[serde(with = "serializers::btreemap_to_bytestring_inplace")] + #[schemars(schema_with = "BTreeMap::::json_schema")] + pub string_data: BTreeMap, +} + +impl Zeroize for SecretTemplate { + #[inline] + fn zeroize(&mut self) { + zeroizers::zeroize_btree_of_bytestring(&mut self.data); + zeroizers::zeroize_btree_of_bytestring(&mut self.string_data); + } +} + +impl Drop for SecretTemplate { + fn drop(&mut self) { + self.zeroize(); + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq))] +pub struct SecretMetadata { + #[serde(default)] + pub annotations: BTreeMap, + #[serde(default)] + pub labels: BTreeMap, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn deser_template_metadata() { + // Full sample + let sample = " + annotations: + my.custom-annotation: value + labels: + some-custom.label: value + "; + let expect = SecretMetadata { + annotations: BTreeMap::from_iter([("my.custom-annotation".into(), "value".into())]), + labels: BTreeMap::from_iter([("some-custom.label".into(), "value".into())]), + }; + let parsed: SecretMetadata = serde_yaml::from_str(sample).unwrap(); + assert_eq!(parsed, expect, "Full sample didn't match"); + + // Empty annotations & labels + let sample = " + annotations: {} + labels: {} + "; + let expect = SecretMetadata { + annotations: BTreeMap::new(), + labels: BTreeMap::new(), + }; + let parsed: SecretMetadata = serde_yaml::from_str(sample).unwrap(); + assert_eq!(parsed, expect, "Empty annotations&labels didn't match"); + + // Empty + let sample = ""; + let expect = SecretMetadata::default(); + let parsed: SecretMetadata = serde_yaml::from_str(sample).unwrap(); + assert_eq!(parsed, expect, "Empty didn't match"); + } + + #[test] + fn deser_template() { + // full sample + let sample = " + data: + key0: YWJjZGU= + stringData: + key1: fghij + metadata: + annotations: + my.custom-annotation: value + labels: + some-custom.label: value + "; + let expect = SecretTemplate { + data: BTreeMap::from_iter([("key0".into(), ByteString("abcde".to_string().into_bytes()))]), + string_data: BTreeMap::from_iter([("key1".into(), ByteString("fghij".to_string().into_bytes()))]), + metadata: SecretMetadata { + annotations: BTreeMap::from_iter([("my.custom-annotation".into(), "value".into())]), + labels: BTreeMap::from_iter([("some-custom.label".into(), "value".into())]), + }, + }; + let parsed: SecretTemplate = serde_yaml::from_str(sample).unwrap(); + assert_eq!(parsed, expect, "Full sample didn't match"); + + // Empty metadata + let sample = " + data: + key0: YWJjZGU= + stringData: + key1: fghij + metadata: {} + "; + let expect = SecretTemplate { + data: BTreeMap::from_iter([("key0".into(), ByteString("abcde".to_string().into_bytes()))]), + string_data: BTreeMap::from_iter([("key1".into(), ByteString("fghij".to_string().into_bytes()))]), + metadata: SecretMetadata::default(), + }; + let parsed: SecretTemplate = serde_yaml::from_str(sample).unwrap(); + assert_eq!(parsed, expect, "Empty metadata didn't match"); + + // No metadata + let sample = " + data: + key0: YWJjZGU= + stringData: + key1: fghij + "; + let expect = SecretTemplate { + data: BTreeMap::from_iter([("key0".into(), ByteString("abcde".to_string().into_bytes()))]), + string_data: BTreeMap::from_iter([("key1".into(), ByteString("fghij".to_string().into_bytes()))]), + metadata: SecretMetadata::default(), + }; + let parsed: SecretTemplate = serde_yaml::from_str(sample).unwrap(); + assert_eq!(parsed, expect, "No metadata didn't match"); + + // Empty data & stringdata + let sample = " + data: {} + stringData: {} + "; + let expect = SecretTemplate { + data: BTreeMap::new(), + string_data: BTreeMap::new(), + metadata: SecretMetadata::default(), + }; + let parsed: SecretTemplate = serde_yaml::from_str(sample).unwrap(); + assert_eq!(parsed, expect, "Empty data&stringData didn't match"); + + // Empty + let sample = ""; + let expect = SecretTemplate::default(); + let parsed: SecretTemplate = serde_yaml::from_str(sample).unwrap(); + assert_eq!(parsed, expect, "Empty didn't match"); + } + + #[test] + fn deser_key_spec() { + // Full password + let sample = " + key: aabbcc + version: 12 + shape: !Password + length: 123 + dictionary: abcdefg_1 + "; + let expect = KeySpec { + key: "aabbcc".into(), + version: 12, + shape: KeyShape::Password { + length: 123, + dictionary: "abcdefg_1".into(), + }, + }; + let parsed: KeySpec = serde_yaml::from_str(sample).unwrap(); + assert_eq!(expect, parsed, "Full password mismatch"); + + // Empty password + let sample = " + key: aabbcc + version: 12 + shape: !Password + "; + let expect = KeySpec { + key: "aabbcc".into(), + version: 12, + shape: KeyShape::Password { + length: KeyShape::default_pwd_length(), + dictionary: KeyShape::default_pwd_dict(), + }, + }; + let parsed: KeySpec = serde_yaml::from_str(sample).unwrap(); + assert_eq!(expect, parsed, "Empty password mismatch"); + + // Full bytes + let sample = " + key: aabbcc + version: 12 + shape: !Bytes + size: 1234 + "; + let expect = KeySpec { + key: "aabbcc".into(), + version: 12, + shape: KeyShape::Bytes { size: 1234 }, + }; + let parsed: KeySpec = serde_yaml::from_str(sample).unwrap(); + assert_eq!(expect, parsed, "Full bytes mismatch"); + + // Empty bytes + let sample = " + key: aabbcc + version: 12 + shape: !Bytes + "; + let expect = KeySpec { + key: "aabbcc".into(), + version: 12, + shape: KeyShape::Bytes { + size: KeyShape::default_bytes_size(), + }, + }; + let parsed: KeySpec = serde_yaml::from_str(sample).unwrap(); + assert_eq!(expect, parsed, "Empty bytes mismatch"); + } +} diff --git a/kuyaseg-controller/src/resource/generated/secret/v1alpha1/pseudorandom.rs b/kuyaseg-controller/src/resource/generated/secret/v1alpha1/pseudorandom.rs new file mode 100644 index 0000000..e69de29 diff --git a/kuyaseg-controller/src/resource/generated/secret/v1alpha1/random.rs b/kuyaseg-controller/src/resource/generated/secret/v1alpha1/random.rs new file mode 100644 index 0000000..e69de29 diff --git a/kuyaseg-controller/src/resource/generated/ssh/mod.rs b/kuyaseg-controller/src/resource/generated/ssh/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/kuyaseg-controller/src/resource/generated/ssh/mod.rs @@ -0,0 +1 @@ + diff --git a/kuyaseg-controller/src/resource/mod.rs b/kuyaseg-controller/src/resource/mod.rs new file mode 100644 index 0000000..743aa41 --- /dev/null +++ b/kuyaseg-controller/src/resource/mod.rs @@ -0,0 +1 @@ +pub mod generated; diff --git a/kuyaseg-controller/src/utils/mod.rs b/kuyaseg-controller/src/utils/mod.rs index 0269ccf..659e158 100644 --- a/kuyaseg-controller/src/utils/mod.rs +++ b/kuyaseg-controller/src/utils/mod.rs @@ -1 +1,2 @@ +pub mod serializers; pub mod zeroizers; diff --git a/kuyaseg-controller/src/utils/serializers.rs b/kuyaseg-controller/src/utils/serializers.rs new file mode 100644 index 0000000..83468f5 --- /dev/null +++ b/kuyaseg-controller/src/utils/serializers.rs @@ -0,0 +1,106 @@ +/// in-place conversion of String into ByteString, verbatim, utf8-lossy +/// +/// e.g. `"abc" -> ByteString("abc".to_string().into_bytes())` +pub mod str_to_bytestring_inplace { + use k8s_openapi::ByteString; + use serde::de::Visitor; + use serde::{ + Deserializer, + Serializer, + }; + + pub fn serialize(value: &ByteString, se: S) -> Result + where + S: Serializer, + { + let value = String::from_utf8_lossy(&value.0); + se.serialize_str(&value) + } + + pub fn deserialize<'de, D>(de: D) -> Result + where + D: Deserializer<'de>, + { + de.deserialize_str(StringBytestringVisitor) + } + + struct StringBytestringVisitor; + + impl<'de> Visitor<'de> for StringBytestringVisitor { + type Value = ByteString; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(ByteString(v.as_bytes().into())) + } + } +} + +/// in-place conversion of BTreeMap to BTreeMap, verbatim, utf8-lossy +/// +///e.g. `{"abc": "def"} -> {"abc": ByteString("def".to_string().into_bytes())}` +pub mod btreemap_to_bytestring_inplace { + use std::collections::BTreeMap; + use std::marker::PhantomData; + + use k8s_openapi::ByteString; + use serde::de::Visitor; + use serde::ser::SerializeMap; + use serde::{ + Deserialize, + Deserializer, + Serialize, + Serializer, + }; + + pub fn serialize(value: &BTreeMap, se: S) -> Result + where + S: Serializer, + K: Serialize, + { + let mut map = se.serialize_map(Some(value.len()))?; + value + .iter() + .try_for_each(|(k, v)| map.serialize_entry(k, &String::from_utf8_lossy(&v.0)))?; + map.end() + } + + pub fn deserialize<'de, K, D>(de: D) -> Result, D::Error> + where + D: Deserializer<'de>, + K: Ord + Deserialize<'de>, + { + de.deserialize_map(BTreeVisitor(PhantomData)) + } + + struct BTreeVisitor(PhantomData); + + impl<'de, K: Ord + Deserialize<'de>> Visitor<'de> for BTreeVisitor { + type Value = BTreeMap; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "map {ty} -> String", ty = std::any::type_name::()) + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut btree = BTreeMap::new(); + while let Some((key, value)) = map.next_entry::()? { + if btree.contains_key(&key) { + return Err(serde::de::Error::custom("Duplicate key")); + } + btree.insert(key, ByteString(value.as_bytes().into())); + } + + Ok(btree) + } + } +} diff --git a/opencode.jsonc b/opencode.jsonc new file mode 100644 index 0000000..ef7fc30 --- /dev/null +++ b/opencode.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "https://opencode.ai/config.json", + "watcher": { + "ignore": [".opencode/node_modules", ".opencode/package.json", ".opencode/package-lock.json"], + }, + "permission": { + "bash": "ask", + "read": { + ".env": "ask", + ".envrc": "ask", + }, + }, +}