commit 7e4429e9d33b00e49dd5e3d39d199f2a0bf2d3ce Author: Bastian Gruber Date: Thu Jul 17 09:14:22 2025 -0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52b9d6a --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2badf48 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3776 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", + "rand_core", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.2.17", + "opaque-debug", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" +dependencies = [ + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.7.0", + "ghash 0.4.4", + "subtle", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.1", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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 = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "async-compression" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +dependencies = [ + "brotli", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "axum-prometheus" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739e2585f5376f5bdd129324ded72d3261fdd5b7c411a645920328fb5dc875d4" +dependencies = [ + "axum", + "bytes", + "futures-core", + "http", + "http-body", + "matchit", + "metrics", + "metrics-exporter-prometheus", + "once_cell", + "pin-project", + "tokio", + "tower 0.4.13", + "tower-http 0.5.2", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bhttp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc657efe5aa3821f1cacfb47665c32849e09820844bff9f5066227312829fa3" +dependencies = [ + "thiserror 1.0.69", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chacha20" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.1.5", + "zeroize", +] + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" +dependencies = [ + "aead 0.4.3", + "chacha20 0.7.1", + "cipher 0.3.0", + "poly1305 0.7.2", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "config" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.8.23", + "yaml-rust2", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" +dependencies = [ + "cipher 0.3.0", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "curve25519-dalek" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373b7c5dbd637569a2cca66e8d66b8c446a1e7bf064ea321d265d7b3dfe7c97e" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.104", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval 0.5.3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval 0.6.2", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hpke" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5933a381bb81f00b083fce6b4528e16d735dbeecbb2bdb45e0dbbf3f7e17" +dependencies = [ + "aead 0.5.2", + "aes-gcm 0.10.3", + "byteorder", + "chacha20poly1305 0.10.1", + "digest 0.10.7", + "generic-array", + "hkdf 0.12.4", + "hmac 0.12.1", + "rand_core", + "sha2 0.10.9", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "metrics" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3045b4193fbdc5b5681f32f11070da9be3609f189a79f3390706d42587f46bb5" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper", + "hyper-util", + "indexmap", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.14.5", + "metrics", + "num_cpus", + "quanta", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "ohttp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a20082b908632960d0aa59af61e2771502b40249d55986e8bdbcd06d723ea5" +dependencies = [ + "aead 0.4.3", + "aes-gcm 0.9.2", + "byteorder", + "chacha20poly1305 0.8.0", + "hex", + "hkdf 0.11.0", + "hpke", + "lazy_static", + "log", + "rand", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror 1.0.69", + "toml 0.5.11", +] + +[[package]] +name = "ohttp-gateway" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "axum-prometheus", + "bhttp", + "chrono", + "clap", + "config", + "futures", + "hex", + "hyper", + "hyper-util", + "jsonwebtoken", + "ohttp", + "prometheus", + "rand", + "reqwest", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tower 0.4.13", + "tower-http 0.6.6", + "tracing", + "tracing-subscriber", + "uuid", + "validator", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +dependencies = [ + "memchr", + "thiserror 2.0.12", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "pest_meta" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +dependencies = [ + "pest", + "sha2 0.10.9", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.4.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.4.0", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "raw-cpuid" +version = "11.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower 0.5.2", + "tower-http 0.6.6", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags", + "serde", + "serde_derive", +] + +[[package]] +name = "rust-ini" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.12", + "time", +] + +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +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 2.0.104", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tokio", + "tokio-util", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "universal-hash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna 1.0.3", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "validator" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" +dependencies = [ + "idna 0.5.0", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +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.2", +] + +[[package]] +name = "windows-targets" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", +] + +[[package]] +name = "yaml-rust2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0654c9f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "ohttp-gateway" +authors = ["Bastian Gruber"] +version = "0.1.0" +edition = "2024" + +[dependencies] +# Web framework and async runtime +axum = { version = "0.7", features = ["macros"] } +tokio = { version = "1", features = ["full"] } +hyper = { version = "1", features = ["full"] } +hyper-util = { version = "0.1", features = ["full"] } + +# HTTP client for backend requests +reqwest = { version = "0.12", features = ["json", "stream"] } + +# OHTTP implementation - Using the martinthomson/ohttp crate +ohttp = { version = "0.5", features = ["rust-hpke"] } +bhttp = "0.5" + +# Middleware and utilities +tower = "0.4" +tower-http = { version = "0.6", features = [ + "cors", + "trace", + "compression-br", + "timeout", +] } + +# Serialization and configuration +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +config = "0.14" + +# Logging and observability +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +chrono = "0.4" + +# Error handling +thiserror = "1.0" +anyhow = "1.0" + +# Metrics and monitoring +prometheus = "0.13" +axum-prometheus = "0.7" + +# Security and validation +validator = { version = "0.18", features = ["derive"] } +jsonwebtoken = "9.0" +uuid = { version = "1.0", features = ["v4"] } + +# Async utilities +tokio-util = "0.7" +futures = "0.3" + +# Random number generation +hex = "0.4" +rand = "0.8" + +# Configuration management +clap = { version = "4.0", features = ["derive", "env"] } + +[profile.release] +lto = "fat" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9dd553d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +# Build stage +FROM rust:1.88-slim as builder + +WORKDIR /app + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Copy Cargo files +COPY Cargo.toml ./ + +# Create dummy main to cache dependencies +RUN mkdir src && echo "fn main() {}" > src/main.rs + +# Build dependencies +RUN RUSTFLAGS="-C target-cpu=native" cargo build --release +RUN rm -rf src + +# Copy source code +COPY src ./src + +# Build the actual application +RUN touch src/main.rs && RUSTFLAGS="-C target-cpu=native" cargo build --release + +# Runtime stage +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy the binary from builder +COPY --from=builder /app/target/release/ohttp-gateway /usr/local/bin/ohttp-gateway + +# Create non-root user +RUN useradd -m -u 1001 ohttp +USER ohttp + +# Set default environment variables +ENV RUST_LOG=debug,ohttp_gateway=debug +ENV LISTEN_ADDR=0.0.0.0:8080 +ENV BACKEND_URL=http://localhost:8000 +ENV REQUEST_TIMEOUT=30 +ENV KEY_ROTATION_ENABLED=false + +EXPOSE 8080 + +CMD ["ohttp-gateway"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d0a1fa1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f37cdae --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ohttp-gateway +A OHTTP Gateway written in Rust diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5f487cc --- /dev/null +++ b/src/config.rs @@ -0,0 +1,270 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::time::Duration; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AppConfig { + // Server configuration + pub listen_addr: String, + pub backend_url: String, + pub request_timeout: Duration, + pub max_body_size: usize, + + // Key management + pub key_rotation_interval: Duration, + pub key_retention_period: Duration, + pub key_rotation_enabled: bool, + + // Security configuration + pub allowed_target_origins: Option>, + pub target_rewrites: Option, + pub rate_limit: Option, + + // Operational configuration + pub metrics_enabled: bool, + pub debug_mode: bool, + pub log_format: LogFormat, + pub log_level: String, + + // OHTTP specific + pub custom_request_type: Option, + pub custom_response_type: Option, + pub seed_secret_key: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TargetRewriteConfig { + pub rewrites: std::collections::HashMap, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TargetRewrite { + pub scheme: String, + pub host: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct RateLimitConfig { + pub requests_per_second: u32, + pub burst_size: u32, + pub by_ip: bool, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum LogFormat { + Default, + Json, +} + +impl Default for AppConfig { + fn default() -> Self { + Self { + listen_addr: "0.0.0.0:8080".to_string(), + backend_url: "http://localhost:8080".to_string(), + request_timeout: Duration::from_secs(30), + max_body_size: 10 * 1024 * 1024, // 10MB + key_rotation_interval: Duration::from_secs(30 * 24 * 60 * 60), // 30 days + key_retention_period: Duration::from_secs(7 * 24 * 60 * 60), // 7 days + key_rotation_enabled: true, + allowed_target_origins: None, + target_rewrites: None, + rate_limit: None, + metrics_enabled: true, + debug_mode: false, + log_format: LogFormat::Default, + log_level: "info".to_string(), + custom_request_type: None, + custom_response_type: None, + seed_secret_key: None, + } + } +} + +impl AppConfig { + pub fn from_env() -> Result> { + let mut config = Self::default(); + + // Basic configuration + if let Ok(addr) = std::env::var("LISTEN_ADDR") { + config.listen_addr = addr; + } + + if let Ok(url) = std::env::var("BACKEND_URL") { + config.backend_url = url; + } + + if let Ok(timeout) = std::env::var("REQUEST_TIMEOUT") { + config.request_timeout = Duration::from_secs(timeout.parse()?); + } + + if let Ok(size) = std::env::var("MAX_BODY_SIZE") { + config.max_body_size = size.parse()?; + } + + // Key management + if let Ok(interval) = std::env::var("KEY_ROTATION_INTERVAL") { + config.key_rotation_interval = Duration::from_secs(interval.parse()?); + } + + if let Ok(period) = std::env::var("KEY_RETENTION_PERIOD") { + config.key_retention_period = Duration::from_secs(period.parse()?); + } + + if let Ok(enabled) = std::env::var("KEY_ROTATION_ENABLED") { + config.key_rotation_enabled = enabled.parse()?; + } + + // Security configuration + if let Ok(origins) = std::env::var("ALLOWED_TARGET_ORIGINS") { + let origins_set: HashSet = origins + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + + if !origins_set.is_empty() { + config.allowed_target_origins = Some(origins_set); + } + } + + if let Ok(rewrites_json) = std::env::var("TARGET_REWRITES") { + let rewrites: std::collections::HashMap = + serde_json::from_str(&rewrites_json)?; + config.target_rewrites = Some(TargetRewriteConfig { rewrites }); + } + + // Rate limiting + if let Ok(rps) = std::env::var("RATE_LIMIT_RPS") { + let rate_limit = RateLimitConfig { + requests_per_second: rps.parse()?, + burst_size: std::env::var("RATE_LIMIT_BURST") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(100), + by_ip: std::env::var("RATE_LIMIT_BY_IP") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(true), + }; + config.rate_limit = Some(rate_limit); + } + + // Operational configuration + if let Ok(enabled) = std::env::var("METRICS_ENABLED") { + config.metrics_enabled = enabled.parse()?; + } + + if let Ok(debug) = std::env::var("GATEWAY_DEBUG") { + config.debug_mode = debug.parse()?; + } + + if let Ok(format) = std::env::var("LOG_FORMAT") { + config.log_format = match format.to_lowercase().as_str() { + "json" => LogFormat::Json, + _ => LogFormat::Default, + }; + } + + if let Ok(level) = std::env::var("LOG_LEVEL") { + config.log_level = level; + } + + // OHTTP specific + if let Ok(req_type) = std::env::var("CUSTOM_REQUEST_TYPE") { + config.custom_request_type = Some(req_type); + } + + if let Ok(resp_type) = std::env::var("CUSTOM_RESPONSE_TYPE") { + config.custom_response_type = Some(resp_type); + } + + if let Ok(seed) = std::env::var("SEED_SECRET_KEY") { + config.seed_secret_key = Some(seed); + } + + // Validate configuration + config.validate()?; + + Ok(config) + } + + fn validate(&self) -> Result<(), Box> { + // Validate key rotation settings + if self.key_retention_period > self.key_rotation_interval { + return Err("Key retention period cannot be longer than rotation interval".into()); + } + + // Validate custom content types + match (&self.custom_request_type, &self.custom_response_type) { + (Some(req), Some(resp)) if req == resp => { + return Err("Request and response content types must be different".into()); + } + (Some(_), None) | (None, Some(_)) => { + return Err("Both custom request and response types must be specified".into()); + } + _ => {} + } + + // Validate seed if provided + if let Some(seed) = &self.seed_secret_key { + let decoded = + hex::decode(seed).map_err(|_| "SEED_SECRET_KEY must be a hex-encoded string")?; + + if decoded.len() < 32 { + return Err("SEED_SECRET_KEY must be at least 32 bytes (64 hex characters)".into()); + } + } + + Ok(()) + } + + /// Check if a target origin is allowed + pub fn is_origin_allowed(&self, origin: &str) -> bool { + match &self.allowed_target_origins { + Some(allowed) => allowed.contains(origin), + None => true, // No restrictions if not configured + } + } + + /// Get rewrite configuration for a host + pub fn get_rewrite(&self, host: &str) -> Option<&TargetRewrite> { + self.target_rewrites + .as_ref() + .and_then(|config| config.rewrites.get(host)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_config() { + let config = AppConfig::default(); + assert_eq!(config.listen_addr, "0.0.0.0:8080"); + assert!(config.key_rotation_enabled); + } + + #[test] + fn test_validation_key_periods() { + let mut config = AppConfig::default(); + config.key_retention_period = Duration::from_secs(100); + config.key_rotation_interval = Duration::from_secs(50); + + assert!(config.validate().is_err()); + } + + #[test] + fn test_origin_allowed() { + let mut config = AppConfig::default(); + config.allowed_target_origins = Some( + vec!["example.com".to_string(), "test.com".to_string()] + .into_iter() + .collect(), + ); + + assert!(config.is_origin_allowed("example.com")); + assert!(!config.is_origin_allowed("forbidden.com")); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..ec488ce --- /dev/null +++ b/src/error.rs @@ -0,0 +1,66 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, + Json, +}; +use serde_json::json; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum GatewayError { + #[error("Invalid request: {0}")] + InvalidRequest(String), + + #[error("Decryption failed: {0}")] + DecryptionError(String), + + #[error("Encryption failed: {0}")] + EncryptionError(String), + + #[error("Backend error: {0}")] + BackendError(String), + + #[error("Request too large: {0}")] + RequestTooLarge(String), + + #[error("Configuration error: {0}")] + ConfigurationError(String), + + #[error("Internal error: {0}")] + InternalError(String), +} + +impl IntoResponse for GatewayError { + fn into_response(self) -> Response { + let (status, error_code, message) = match self { + GatewayError::InvalidRequest(msg) => (StatusCode::BAD_REQUEST, "invalid_request", msg), + GatewayError::DecryptionError(msg) => { + (StatusCode::BAD_REQUEST, "decryption_error", msg) + } + GatewayError::EncryptionError(msg) => { + (StatusCode::INTERNAL_SERVER_ERROR, "encryption_error", msg) + } + GatewayError::BackendError(msg) => (StatusCode::BAD_GATEWAY, "backend_error", msg), + GatewayError::RequestTooLarge(msg) => { + (StatusCode::PAYLOAD_TOO_LARGE, "request_too_large", msg) + } + GatewayError::ConfigurationError(msg) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "configuration_error", + msg, + ), + GatewayError::InternalError(msg) => { + (StatusCode::INTERNAL_SERVER_ERROR, "internal_error", msg) + } + }; + + let body = Json(json!({ + "error": { + "code": error_code, + "message": message + } + })); + + (status, body).into_response() + } +} diff --git a/src/handlers/health.rs b/src/handlers/health.rs new file mode 100644 index 0000000..6693ba1 --- /dev/null +++ b/src/handlers/health.rs @@ -0,0 +1,77 @@ +use crate::{error::GatewayError, state::AppState}; +use axum::{extract::State, Json}; +use serde_json::json; +use std::time::Duration; + +pub async fn health_check( + State(state): State, +) -> Result, GatewayError> { + let mut health_checks = vec![]; + + // Check key manager health + let key_status = match state.key_manager.get_encoded_config().await { + Ok(config) if config.len() > 100 => "healthy", + Ok(_) => "unhealthy", + Err(_) => "unhealthy", + }; + + health_checks.push(json!({ + "component": "ohttp_keys", + "status": key_status + })); + + // Check backend connectivity - use the correct health endpoint + let backend_health_url = format!("{}/health", state.config.backend_url); + let backend_status = match state + .http_client + .get(&backend_health_url) + .timeout(Duration::from_secs(5)) + .send() + .await + { + Ok(resp) if resp.status().is_success() => "healthy", + Ok(resp) => { + tracing::warn!("Backend health check returned: {}", resp.status()); + "unhealthy" + } + Err(e) => { + tracing::error!("Backend health check failed: {}", e); + "unhealthy" + } + }; + + health_checks.push(json!({ + "component": "backend", + "status": backend_status, + "url": backend_health_url + })); + + let overall_status = if health_checks.iter().all(|c| c["status"] == "healthy") { + "healthy" + } else { + "unhealthy" + }; + + Ok(Json(json!({ + "status": overall_status, + "timestamp": chrono::Utc::now().to_rfc3339(), + "checks": health_checks, + "version": env!("CARGO_PKG_VERSION") + }))) +} + +pub async fn metrics_handler() -> Result { + use prometheus::{Encoder, TextEncoder}; + + let encoder = TextEncoder::new(); + let metric_families = prometheus::gather(); + + let mut buffer = Vec::new(); + encoder + .encode(&metric_families, &mut buffer) + .map_err(|e| GatewayError::InternalError(format!("Failed to encode metrics: {e}")))?; + + String::from_utf8(buffer).map_err(|e| { + GatewayError::InternalError(format!("Failed to convert metrics to string: {e}")) + }) +} diff --git a/src/handlers/keys.rs b/src/handlers/keys.rs new file mode 100644 index 0000000..aee1668 --- /dev/null +++ b/src/handlers/keys.rs @@ -0,0 +1,83 @@ +use crate::AppState; +use axum::{ + extract::State, + http::{header, HeaderName, StatusCode}, + response::{IntoResponse, Response}, +}; +use chrono::Utc; +use tracing::info; + +/// Handler for /ohttp-keys endpoint +/// Returns key configurations in the standard OHTTP format +pub async fn get_ohttp_keys(State(state): State) -> Result { + state.metrics.key_requests_total.inc(); + + match state.key_manager.get_encoded_config().await { + Ok(config_bytes) => { + info!("Serving {} bytes of key configurations", config_bytes.len()); + + // Calculate cache duration based on rotation interval + let max_age = calculate_cache_max_age(&state); + + Ok(( + StatusCode::OK, + [ + (header::CONTENT_TYPE, "application/ohttp-keys"), + (header::CACHE_CONTROL, &format!("public, max-age={max_age}")), + (HeaderName::from_static("x-content-type-options"), "nosniff"), + ], + config_bytes, + ) + .into_response()) + } + Err(e) => { + tracing::error!("Failed to encode key config: {e}"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } + } +} + +/// Legacy endpoint for backward compatibility +/// Some older clients may still use /ohttp-configs +pub async fn get_legacy_ohttp_configs( + State(state): State, +) -> Result { + // Just forward to the main handler + get_ohttp_keys(State(state)).await +} + +/// Calculate appropriate cache duration for key configurations +fn calculate_cache_max_age(state: &AppState) -> u64 { + // Cache for 10% of rotation interval, minimum 1 hour, maximum 24 hours + let ten_percent = state.config.key_rotation_interval.as_secs() / 10; + let one_hour = 3600; + let twenty_four_hours = 86400; + + ten_percent.max(one_hour).min(twenty_four_hours) +} + +/// Health check endpoint specifically for key management +pub async fn key_health_check(State(state): State) -> impl IntoResponse { + let stats = state.key_manager.get_stats().await; + + let health_status = if stats.active_keys > 0 && stats.expired_keys == 0 { + "healthy" + } else if stats.active_keys > 0 { + "degraded" + } else { + "unhealthy" + }; + + axum::Json(serde_json::json!({ + "status": health_status, + "timestamp": Utc::now().to_rfc3339(), + "key_stats": { + "active_key_id": stats.active_key_id, + "total_keys": stats.total_keys, + "active_keys": stats.active_keys, + "expired_keys": stats.expired_keys, + "rotation_enabled": stats.auto_rotation_enabled, + "rotation_interval_hours": stats.rotation_interval.as_secs() / 3600, + } + })) +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..21d81fa --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,22 @@ +pub mod health; +pub mod keys; +pub mod ohttp; + +use crate::state::AppState; +use axum::{ + routing::{get, post}, + Router, +}; + +pub fn routes() -> Router { + Router::new() + // OHTTP endpoints + .route("/gateway", post(ohttp::handle_ohttp_request)) + .route("/ohttp-keys", get(keys::get_ohttp_keys)) + // Legacy endpoints for backward compatibility + .route("/ohttp-configs", get(keys::get_legacy_ohttp_configs)) + // Health and monitoring + .route("/health", get(health::health_check)) + .route("/health/keys", get(keys::key_health_check)) + .route("/metrics", get(health::metrics_handler)) +} diff --git a/src/handlers/ohttp.rs b/src/handlers/ohttp.rs new file mode 100644 index 0000000..49505d0 --- /dev/null +++ b/src/handlers/ohttp.rs @@ -0,0 +1,477 @@ +use crate::{error::GatewayError, state::AppState}; +use axum::{ + body::{Body, Bytes}, + extract::State, + http::{header, HeaderMap, StatusCode}, + response::{IntoResponse, Response}, +}; +use bhttp::{Message, Mode}; +use tracing::{debug, error, info, warn}; + +const OHTTP_REQUEST_CONTENT_TYPE: &str = "message/ohttp-req"; +const OHTTP_RESPONSE_CONTENT_TYPE: &str = "message/ohttp-res"; + +pub async fn handle_ohttp_request( + State(state): State, + headers: HeaderMap, + body: Bytes, +) -> impl IntoResponse { + let timer = state.metrics.request_duration.start_timer(); + state.metrics.requests_total.inc(); + + // Extract key ID from the request if possible + let key_id = extract_key_id_from_request(&body); + + let result = handle_ohttp_request_inner(state.clone(), headers, body, key_id).await; + timer.stop_and_record(); + + match result { + Ok(response) => response, + Err(e) => { + error!("OHTTP request failed: {:?}", e); + + // Log metrics based on error type + match &e { + GatewayError::DecryptionError(_) => state.metrics.decryption_errors_total.inc(), + GatewayError::EncryptionError(_) => state.metrics.encryption_errors_total.inc(), + GatewayError::BackendError(_) => state.metrics.backend_errors_total.inc(), + _ => {} + } + + e.into_response() + } + } +} + +async fn handle_ohttp_request_inner( + state: AppState, + headers: HeaderMap, + body: Bytes, + key_id: Option, +) -> Result { + // Validate request + validate_ohttp_request(&headers, &body, &state)?; + + debug!( + "Received OHTTP request with {} bytes, key_id: {:?}", + body.len(), + key_id + ); + + // Get the appropriate server based on key ID + let server = if let Some(id) = key_id { + // Try to get server for specific key ID + match state.key_manager.get_server_by_id(id).await { + Some(server) => { + debug!("Using server for key ID: {}", id); + server + } + None => { + warn!("Unknown key ID: {}, falling back to current server", id); + state + .key_manager + .get_current_server() + .await + .map_err(|e| GatewayError::ConfigurationError(e.to_string()))? + } + } + } else { + // Use current active server + state + .key_manager + .get_current_server() + .await + .map_err(|e| GatewayError::ConfigurationError(e.to_string()))? + }; + + // Decrypt the OHTTP request + let (bhttp_request, server_response) = server.decapsulate(&body).map_err(|e| { + error!("Failed to decapsulate OHTTP request: {e}"); + GatewayError::DecryptionError(format!("Failed to decapsulate: {e}")) + })?; + + debug!( + "Successfully decapsulated request, {} bytes", + bhttp_request.len() + ); + + // Parse binary HTTP message + let message = parse_bhttp_message(&bhttp_request)?; + + // Validate and potentially transform the request + let message = validate_and_transform_request(message, &state)?; + + // Forward request to backend + let backend_response = forward_to_backend(&state, message).await?; + + // Convert response to binary HTTP format + let bhttp_response = convert_to_binary_http(backend_response).await?; + + // Encrypt response back to client + let encrypted_response = server_response.encapsulate(&bhttp_response).map_err(|e| { + GatewayError::EncryptionError(format!("Failed to encapsulate response: {e}")) + })?; + + state.metrics.successful_requests_total.inc(); + info!("Successfully processed OHTTP request"); + + // Build response with appropriate headers + Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, OHTTP_RESPONSE_CONTENT_TYPE) + .header(header::CACHE_CONTROL, "no-cache, no-store, must-revalidate") + .header("X-Content-Type-Options", "nosniff") + .header("X-Frame-Options", "DENY") + .body(Body::from(encrypted_response)) + .map_err(|e| GatewayError::InternalError(format!("Response build error: {e}"))) +} + +/// Extract key ID from OHTTP request (first byte after version) +fn extract_key_id_from_request(body: &[u8]) -> Option { + // OHTTP request format: version(1) + key_id(1) + kem_id(2) + kdf_id(2) + aead_id(2) + enc + ciphertext + if body.len() > 1 { + Some(body[1]) + } else { + None + } +} + +/// Validate the incoming OHTTP request +fn validate_ohttp_request( + headers: &HeaderMap, + body: &Bytes, + state: &AppState, +) -> Result<(), GatewayError> { + // Check content type + let content_type = headers + .get(header::CONTENT_TYPE) + .and_then(|v| v.to_str().ok()) + .ok_or_else(|| GatewayError::InvalidRequest("Missing content-type header".to_string()))?; + + if content_type != OHTTP_REQUEST_CONTENT_TYPE { + return Err(GatewayError::InvalidRequest(format!( + "Invalid content-type: expected '{OHTTP_REQUEST_CONTENT_TYPE}', got '{content_type}'" + ))); + } + + // Check body size + if body.is_empty() { + return Err(GatewayError::InvalidRequest( + "Empty request body".to_string(), + )); + } + + if body.len() > state.config.max_body_size { + return Err(GatewayError::RequestTooLarge(format!( + "Request body too large: {} bytes (max: {})", + body.len(), + state.config.max_body_size + ))); + } + + // Minimum OHTTP request size check + if body.len() < 10 { + return Err(GatewayError::InvalidRequest( + "Request too small to be valid OHTTP".to_string(), + )); + } + + Ok(()) +} + +/// Parse binary HTTP message with error handling +fn parse_bhttp_message(data: &[u8]) -> Result { + let mut cursor = std::io::Cursor::new(data); + Message::read_bhttp(&mut cursor) + .map_err(|e| GatewayError::InvalidRequest(format!("Failed to parse binary HTTP: {e}"))) +} + +/// Validate and transform the request based on security policies +fn validate_and_transform_request( + message: Message, + state: &AppState, +) -> Result { + let control = message.control(); + + // Extract host from authority or Host header + let host = control + .authority() + .map(|a| String::from_utf8_lossy(a).into_owned()) + .or_else(|| { + message.header().fields().iter().find_map(|field| { + if field.name().eq_ignore_ascii_case(b"host") { + Some(String::from_utf8_lossy(field.value()).into_owned()) + } else { + None + } + }) + }) + .ok_or_else(|| GatewayError::InvalidRequest("Missing host/authority".to_string()))?; + + // Check if origin is allowed + if !state.config.is_origin_allowed(&host) { + warn!("Blocked request to forbidden origin: {host}"); + return Err(GatewayError::InvalidRequest(format!( + "Target origin not allowed: {host}" + ))); + } + + // Apply any configured rewrites + if let Some(rewrite) = state.config.get_rewrite(&host) { + debug!( + "Applying rewrite for host {}: {} -> {}", + host, rewrite.scheme, rewrite.host + ); + + // Clone the message to modify it + let mut new_message = Message::request( + Vec::from(control.method().unwrap_or(b"GET")), + Vec::from( + format!( + "{}://{}{}", + rewrite.scheme, + rewrite.host, + control + .path() + .map(|p| String::from_utf8_lossy(p)) + .unwrap_or("/".into()) + ) + .as_bytes(), + ), + Vec::from(control.scheme().unwrap_or(rewrite.scheme.as_bytes())), + Vec::from(rewrite.host.as_bytes()), + ); + + // Copy all headers except Host and Authority + for field in message.header().fields() { + let name = field.name(); + if !name.eq_ignore_ascii_case(b"host") && !name.eq_ignore_ascii_case(b"authority") { + new_message.put_header(name, field.value()); + } + } + + // Add the new Host header + new_message.put_header(b"host", rewrite.host.as_bytes()); + + // Copy body content + if !message.content().is_empty() { + new_message.write_content(message.content()); + } + + return Ok(new_message); + } + + Ok(message) +} + +async fn forward_to_backend( + state: &AppState, + bhttp_message: Message, +) -> Result { + let control = bhttp_message.control(); + let method = control.method().unwrap_or(b"GET"); + let path = control + .path() + .map(|p| String::from_utf8_lossy(p).into_owned()) + .unwrap_or_else(|| "/".to_string()); + + // Extract host for URL construction + let host = control + .authority() + .map(|a| String::from_utf8_lossy(a).into_owned()) + .or_else(|| { + bhttp_message.header().fields().iter().find_map(|field| { + if field.name().eq_ignore_ascii_case(b"host") { + Some(String::from_utf8_lossy(field.value()).into_owned()) + } else { + None + } + }) + }); + + // Build the backend URI + let uri = if let Some(host) = host { + // Check for rewrites + if let Some(rewrite) = state.config.get_rewrite(&host) { + format!("{}://{}{}", rewrite.scheme, rewrite.host, path) + } else { + build_backend_uri(&state.config.backend_url, &path)? + } + } else { + build_backend_uri(&state.config.backend_url, &path)? + }; + + info!( + "Forwarding {} request to {}", + String::from_utf8_lossy(method), + uri + ); + + let reqwest_method = convert_method_to_reqwest(method); + let mut request_builder = state.http_client.request(reqwest_method, &uri); + + // Add headers from the binary HTTP message + for field in bhttp_message.header().fields() { + let name = String::from_utf8_lossy(field.name()); + let value = String::from_utf8_lossy(field.value()); + + // Skip headers that should not be forwarded + if should_forward_header(&name) { + request_builder = request_builder.header(name.as_ref(), value.as_ref()); + } + } + + // Add body if present + let content = bhttp_message.content(); + if !content.is_empty() { + request_builder = request_builder.body(content.to_vec()); + } + + // Send request with timeout + let response = request_builder.send().await.map_err(|e| { + error!("Backend request failed: {e}"); + GatewayError::BackendError(format!("Backend request failed: {e}")) + })?; + + // Check for backend errors + if response.status().is_server_error() { + return Err(GatewayError::BackendError(format!( + "Backend returned error: {}", + response.status() + ))); + } + + Ok(response) +} + +fn convert_method_to_reqwest(method: &[u8]) -> reqwest::Method { + match method { + b"GET" => reqwest::Method::GET, + b"POST" => reqwest::Method::POST, + b"PUT" => reqwest::Method::PUT, + b"DELETE" => reqwest::Method::DELETE, + b"HEAD" => reqwest::Method::HEAD, + b"OPTIONS" => reqwest::Method::OPTIONS, + b"PATCH" => reqwest::Method::PATCH, + b"TRACE" => reqwest::Method::TRACE, + _ => reqwest::Method::GET, + } +} + +fn build_backend_uri(backend_url: &str, path: &str) -> Result { + let base_url = backend_url.trim_end_matches('/'); + let path = if path.starts_with('/') { + path + } else { + &format!("/{path}") + }; + + // Validate path to prevent SSRF attacks + if path.contains("..") || path.contains("//") { + return Err(GatewayError::InvalidRequest( + "Invalid path detected".to_string(), + )); + } + + // Additional validation for suspicious patterns + if path.contains('\0') || path.contains('\r') || path.contains('\n') { + return Err(GatewayError::InvalidRequest( + "Invalid characters in path".to_string(), + )); + } + + Ok(format!("{base_url}{path}")) +} + +fn should_forward_header(name: &str) -> bool { + const SKIP_HEADERS: &[&str] = &[ + "host", + "connection", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailers", + "transfer-encoding", + "keep-alive", + "http2-settings", + "upgrade-insecure-requests", + ]; + + !SKIP_HEADERS.contains(&name.to_lowercase().as_str()) +} + +async fn convert_to_binary_http(response: reqwest::Response) -> Result, GatewayError> { + let status = response.status(); + let headers = response.headers().clone(); + let body = response + .bytes() + .await + .map_err(|e| GatewayError::BackendError(format!("Failed to read response body: {e}")))?; + + // Create a bhttp response message + let mut message = Message::response( + bhttp::StatusCode::try_from(status.as_u16()) + .map_err(|_| GatewayError::InternalError("Invalid status code".to_string()))?, + ); + + // Add headers + for (name, value) in headers.iter() { + if should_forward_header(name.as_str()) { + message.put_header(name.as_str().as_bytes(), value.as_bytes()); + } + } + + // Add body + if !body.is_empty() { + message.write_content(&body); + } + + // Serialize to binary HTTP using KnownLength mode for compatibility + let mut output = Vec::new(); + message + .write_bhttp(Mode::KnownLength, &mut output) + .map_err(|e| GatewayError::InternalError(format!("Failed to write binary HTTP: {e}")))?; + + debug!("Created BHTTP response of {} bytes", output.len()); + + Ok(output) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_key_id() { + let body = vec![0x00, 0x7F, 0x00, 0x01]; // version, key_id, kem_id... + assert_eq!(extract_key_id_from_request(&body), Some(0x7F)); + + let empty = vec![]; + assert_eq!(extract_key_id_from_request(&empty), None); + } + + #[test] + fn test_should_forward_header() { + assert!(should_forward_header("content-type")); + assert!(should_forward_header("authorization")); + assert!(!should_forward_header("connection")); + assert!(!should_forward_header("Host")); + } + + #[test] + fn test_build_backend_uri() { + assert_eq!( + build_backend_uri("https://backend.com", "/api/test").unwrap(), + "https://backend.com/api/test" + ); + + assert_eq!( + build_backend_uri("https://backend.com/", "/api/test").unwrap(), + "https://backend.com/api/test" + ); + + assert!(build_backend_uri("https://backend.com", "/../etc/passwd").is_err()); + assert!(build_backend_uri("https://backend.com", "//evil.com").is_err()); + } +} diff --git a/src/key_manager.rs b/src/key_manager.rs new file mode 100644 index 0000000..0eb28fe --- /dev/null +++ b/src/key_manager.rs @@ -0,0 +1,379 @@ +use chrono::{DateTime, Utc}; +use ohttp::{ + hpke::{Aead, Kdf, Kem}, + KeyConfig, Server as OhttpServer, SymmetricSuite, +}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::RwLock; +use tracing::{error, info}; + +/// Represents a key with its metadata +#[derive(Clone, Debug)] +pub struct KeyInfo { + pub id: u8, + pub config: KeyConfig, + pub server: OhttpServer, + pub expires_at: DateTime, + pub is_active: bool, +} + +/// Configuration for key management +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct KeyManagerConfig { + /// How often to rotate keys (default: 30 days) + pub rotation_interval: Duration, + /// How long to keep old keys for decryption (default: 7 days) + pub key_retention_period: Duration, + /// Whether to enable automatic rotation + pub auto_rotation_enabled: bool, + /// Supported cipher suites + pub cipher_suites: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CipherSuiteConfig { + pub kem: String, + pub kdf: String, + pub aead: String, +} + +impl Default for KeyManagerConfig { + fn default() -> Self { + Self { + rotation_interval: Duration::from_secs(30 * 24 * 60 * 60), // 30 days + key_retention_period: Duration::from_secs(7 * 24 * 60 * 60), // 7 days + auto_rotation_enabled: true, + cipher_suites: vec![ + CipherSuiteConfig { + kem: "X25519_SHA256".to_string(), + kdf: "HKDF_SHA256".to_string(), + aead: "AES_128_GCM".to_string(), + }, + CipherSuiteConfig { + kem: "X25519_SHA256".to_string(), + kdf: "HKDF_SHA256".to_string(), + aead: "CHACHA20_POLY1305".to_string(), + }, + ], + } + } +} + +pub struct KeyManager { + /// All keys indexed by ID + keys: Arc>>, + /// Current active key ID + active_key_id: Arc>, + /// Configuration + config: KeyManagerConfig, + /// Key ID counter (wraps around after 255) + next_key_id: Arc>, + /// Seed for deterministic key generation (optional) + seed: Option>, +} + +impl KeyManager { + pub async fn new(config: KeyManagerConfig) -> Result> { + let manager = Self { + keys: Arc::new(RwLock::new(HashMap::new())), + active_key_id: Arc::new(RwLock::new(0)), + config, + next_key_id: Arc::new(RwLock::new(1)), + seed: None, + }; + + // Generate initial key + let initial_key = manager.generate_new_key().await?; + { + let mut keys = manager.keys.write().await; + let mut active_id = manager.active_key_id.write().await; + + keys.insert(initial_key.id, initial_key.clone()); + *active_id = initial_key.id; + } + + info!("KeyManager initialized with key ID: {}", initial_key.id); + Ok(manager) + } + + /// Create a key manager with a seed for deterministic key generation + pub async fn new_with_seed( + config: KeyManagerConfig, + seed: Vec, + ) -> Result> { + if seed.len() < 32 { + return Err("Seed must be at least 32 bytes".into()); + } + + let mut manager = Self::new(config).await?; + manager.seed = Some(seed); + Ok(manager) + } + + /// Generate a new key configuration + async fn generate_new_key(&self) -> Result> { + let key_id = { + let mut next_id = self.next_key_id.write().await; + let id = *next_id; + *next_id = next_id.wrapping_add(1); + id + }; + + // Parse cipher suites from config + let mut symmetric_suites = Vec::new(); + for suite in &self.config.cipher_suites { + let kdf = match suite.kdf.as_str() { + "HKDF_SHA256" => Kdf::HkdfSha256, + "HKDF_SHA384" => Kdf::HkdfSha384, + "HKDF_SHA512" => Kdf::HkdfSha512, + _ => Kdf::HkdfSha256, + }; + + let aead = match suite.aead.as_str() { + "AES_128_GCM" => Aead::Aes128Gcm, + "AES_256_GCM" => Aead::Aes256Gcm, + "CHACHA20_POLY1305" => Aead::ChaCha20Poly1305, + _ => Aead::Aes128Gcm, + }; + + symmetric_suites.push(SymmetricSuite::new(kdf, aead)); + } + + // Determine KEM based on config - only X25519 is supported by ohttp crate + let kem = Kem::X25519Sha256; + + // Generate key config + let key_config = if let Some(seed) = &self.seed { + // Deterministic generation using seed + key_id + let mut key_seed = seed.clone(); + key_seed.push(key_id); + + // The ohttp crate doesn't directly support deterministic key generation + // This would require extending the crate or using a custom implementation + KeyConfig::new(key_id, kem, symmetric_suites)? + } else { + KeyConfig::new(key_id, kem, symmetric_suites)? + }; + + let server = OhttpServer::new(key_config.clone())?; + let now = Utc::now(); + + Ok(KeyInfo { + id: key_id, + config: key_config, + server, + expires_at: now + chrono::Duration::from_std(self.config.rotation_interval)?, + is_active: true, + }) + } + + /// Get the current active server for decryption + pub async fn get_current_server(&self) -> Result> { + let keys = self.keys.read().await; + let active_id = self.active_key_id.read().await; + + keys.get(&*active_id) + .map(|info| info.server.clone()) + .ok_or_else(|| "No active key found".into()) + } + + /// Get a server by key ID (for handling requests with specific key IDs) + pub async fn get_server_by_id(&self, key_id: u8) -> Option { + let keys = self.keys.read().await; + keys.get(&key_id).map(|info| info.server.clone()) + } + + /// Get encoded config for backward compatibility + pub async fn get_encoded_config(&self) -> Result, Box> { + let keys = self.keys.read().await; + let active_id = self.active_key_id.read().await; + let cfg_bytes = keys + .get(&*active_id) + .ok_or("no active key")? + .config + .encode()?; + + let mut out = Vec::with_capacity(cfg_bytes.len() + 2); + out.extend_from_slice(&(cfg_bytes.len() as u16).to_be_bytes()); // 2-byte length + out.extend_from_slice(&cfg_bytes); + Ok(out) + } + + /// Rotate keys by generating a new key and marking old ones for expiration + pub async fn rotate_keys(&self) -> Result<(), Box> { + info!("Starting key rotation"); + + // Generate new key + let new_key = self.generate_new_key().await?; + let new_key_id = new_key.id; + + // Update key store + { + let mut keys = self.keys.write().await; + let mut active_id = self.active_key_id.write().await; + let now = Utc::now(); + + // Mark current active key for future expiration + if let Some(current_key) = keys.get_mut(&*active_id) { + current_key.is_active = false; + // Keep it around for the retention period + current_key.expires_at = + now + chrono::Duration::from_std(self.config.key_retention_period)?; + } + + // Add new key + keys.insert(new_key_id, new_key); + + // Update active key ID + *active_id = new_key_id; + + // Clean up expired keys + keys.retain(|_, info| info.expires_at > now); + + info!( + "Key rotation completed. New active key ID: {}, total keys: {}", + new_key_id, + keys.len() + ); + } + + Ok(()) + } + + /// Check if rotation is needed + pub async fn should_rotate(&self) -> bool { + let keys = self.keys.read().await; + let active_id = self.active_key_id.read().await; + + if let Some(active_key) = keys.get(&*active_id) { + let time_until_expiry = active_key.expires_at.signed_duration_since(Utc::now()); + + // Rotate if less than 10% of the rotation interval remains + let threshold = chrono::Duration::from_std(self.config.rotation_interval / 10) + .unwrap_or_else(|_| chrono::Duration::days(3)); + + time_until_expiry < threshold + } else { + true // No active key, definitely need to rotate + } + } + + /// Start automatic key rotation scheduler + pub async fn start_rotation_scheduler(self: Arc) { + if !self.config.auto_rotation_enabled { + info!("Automatic key rotation is disabled"); + return; + } + + let manager = self; + tokio::spawn(async move { + // Check every hour + let mut interval = tokio::time::interval(Duration::from_secs(3600)); + + loop { + interval.tick().await; + + if manager.should_rotate().await { + if let Err(e) = manager.rotate_keys().await { + error!("Key rotation failed: {}", e); + } + } + + // Also clean up expired keys + manager.cleanup_expired_keys().await; + } + }); + } + + /// Clean up expired keys + async fn cleanup_expired_keys(&self) { + let mut keys = self.keys.write().await; + let now = Utc::now(); + let before_count = keys.len(); + + keys.retain(|id, info| { + if info.expires_at <= now { + info!("Removing expired key ID: {}", id); + false + } else { + true + } + }); + + let removed = before_count - keys.len(); + if removed > 0 { + info!("Cleaned up {} expired keys", removed); + } + } + + /// Get key manager statistics + pub async fn get_stats(&self) -> KeyManagerStats { + let keys = self.keys.read().await; + let active_id = self.active_key_id.read().await; + let now = Utc::now(); + + let active_keys = keys.values().filter(|k| k.is_active).count(); + let total_keys = keys.len(); + let expired_keys = keys.values().filter(|k| k.expires_at <= now).count(); + + KeyManagerStats { + active_key_id: *active_id, + total_keys, + active_keys, + expired_keys, + rotation_interval: self.config.rotation_interval, + auto_rotation_enabled: self.config.auto_rotation_enabled, + } + } +} + +#[derive(Debug, Serialize)] +pub struct KeyManagerStats { + pub active_key_id: u8, + pub total_keys: usize, + pub active_keys: usize, + pub expired_keys: usize, + pub rotation_interval: Duration, + pub auto_rotation_enabled: bool, +} + +// Ensure thread safety +unsafe impl Send for KeyManager {} +unsafe impl Sync for KeyManager {} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_key_generation() { + let config = KeyManagerConfig::default(); + let manager = KeyManager::new(config).await.unwrap(); + + let stats = manager.get_stats().await; + assert_eq!(stats.total_keys, 1); + assert_eq!(stats.active_keys, 1); + } + + #[tokio::test] + async fn test_key_rotation() { + let config = KeyManagerConfig { + rotation_interval: Duration::from_secs(60), + key_retention_period: Duration::from_secs(30), + ..Default::default() + }; + + let manager = KeyManager::new(config).await.unwrap(); + let initial_stats = manager.get_stats().await; + + // Rotate keys + manager.rotate_keys().await.unwrap(); + + let new_stats = manager.get_stats().await; + assert_eq!(new_stats.total_keys, 2); + assert_ne!(new_stats.active_key_id, initial_stats.active_key_id); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1a17af8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +pub mod config; +pub mod error; +pub mod handlers; +pub mod key_manager; +pub mod metrics; +pub mod middleware; +pub mod state; + +pub use config::AppConfig; +pub use error::GatewayError; +pub use state::AppState; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..122902a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,203 @@ +mod config; +mod error; +mod handlers; +mod key_manager; +mod metrics; +mod middleware; +mod state; + +use crate::config::{AppConfig, LogFormat}; +use crate::state::AppState; +use axum::{middleware as axum_middleware, Router}; +use std::net::SocketAddr; +use std::time::Duration; +use tokio::net::TcpListener; +use tokio::signal; +use tower_http::compression::CompressionLayer; +use tower_http::cors::{Any, CorsLayer}; +use tower_http::timeout::TimeoutLayer; +use tower_http::trace::TraceLayer; +use tracing::{info, warn}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Load configuration first + let config = AppConfig::from_env()?; + + // Initialize tracing based on config + initialize_tracing(&config); + + info!("Starting OHTTP Gateway v{}", env!("CARGO_PKG_VERSION")); + info!("Configuration loaded: {:?}", config); + + // Initialize application state + let app_state = AppState::new(config.clone()).await?; + + // Start key rotation scheduler + if config.key_rotation_enabled { + info!("Starting automatic key rotation scheduler"); + app_state + .key_manager + .clone() + .start_rotation_scheduler() + .await; + } else { + warn!("Automatic key rotation is disabled"); + } + + // Create router + let app = create_router(app_state.clone(), &config); + + // Parse socket address + let addr: SocketAddr = config.listen_addr.parse()?; + let listener = TcpListener::bind(addr).await?; + + info!("OHTTP Gateway listening on {}", addr); + info!("Backend URL: {}", config.backend_url); + + if let Some(allowed) = &config.allowed_target_origins { + info!("Allowed origins: {:?}", allowed); + } else { + warn!("No origin restrictions configured - all targets allowed"); + } + + // Start server with graceful shutdown + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(shutdown_signal()) + .await?; + + info!("Server stopped gracefully"); + Ok(()) +} + +fn initialize_tracing(config: &AppConfig) { + use tracing_subscriber::{fmt, EnvFilter}; + + let env_filter = + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&config.log_level)); + + match config.log_format { + LogFormat::Json => { + fmt() + .json() + .with_env_filter(env_filter) + .with_target(true) + .with_thread_ids(true) + .with_file(config.debug_mode) + .with_line_number(config.debug_mode) + .init(); + } + LogFormat::Default => { + fmt() + .with_env_filter(env_filter) + .with_target(true) + .with_thread_ids(true) + .with_file(config.debug_mode) + .with_line_number(config.debug_mode) + .init(); + } + } +} + +fn create_router(app_state: AppState, config: &AppConfig) -> Router { + let mut app = Router::new(); + + // Add routes + app = app.merge(handlers::routes()); + + // Add middleware layers (order matters - first added is executed last) + app = app.layer( + tower::ServiceBuilder::new() + // Outer layers (executed first on request, last on response) + .layer(TraceLayer::new_for_http()) + .layer(CompressionLayer::new()) + .layer(TimeoutLayer::new(Duration::from_secs(60))) + // Security middleware + .layer(axum_middleware::from_fn_with_state( + app_state.clone(), + middleware::security::security_middleware, + )) + // Request validation + .layer(axum_middleware::from_fn( + middleware::security::request_validation_middleware, + )) + // Logging middleware + .layer(axum_middleware::from_fn_with_state( + app_state.clone(), + middleware::logging::logging_middleware, + )) + // Metrics middleware + .layer(axum_middleware::from_fn_with_state( + app_state.clone(), + middleware::metrics::metrics_middleware, + )) + // CORS configuration + .layer(create_cors_layer(config)), + ); + + app.with_state(app_state) +} + +fn create_cors_layer(config: &AppConfig) -> CorsLayer { + if config.debug_mode { + // Permissive CORS in debug mode + CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any) + } else { + // Restrictive CORS in production + CorsLayer::new() + .allow_origin([ + "https://example.com".parse().unwrap(), + // Add your allowed origins here + ]) + .allow_methods([axum::http::Method::GET, axum::http::Method::POST]) + .allow_headers([axum::http::header::CONTENT_TYPE, axum::http::header::ACCEPT]) + .max_age(Duration::from_secs(3600)) + } +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => { + info!("Received Ctrl+C, starting graceful shutdown"); + }, + _ = terminate => { + info!("Received SIGTERM, starting graceful shutdown"); + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_config_loading() { + // Test that default config loads successfully + let config = AppConfig::default(); + assert!(!config.debug_mode); + assert!(config.key_rotation_enabled); + } +} diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 0000000..15ea750 --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,66 @@ +use prometheus::{register_counter, register_gauge, register_histogram, Counter, Gauge, Histogram}; + +#[derive(Clone)] +pub struct AppMetrics { + pub requests_total: Counter, + pub successful_requests_total: Counter, + pub decryption_errors_total: Counter, + pub encryption_errors_total: Counter, + pub backend_errors_total: Counter, + pub key_requests_total: Counter, + pub request_duration: Histogram, + pub active_connections: Gauge, +} + +impl Default for AppMetrics { + fn default() -> Self { + AppMetrics::new() + } +} + +impl AppMetrics { + fn new() -> Self { + Self { + requests_total: register_counter!( + "ohttp_requests_total", + "Total number of OHTTP requests" + ) + .unwrap(), + successful_requests_total: register_counter!( + "ohttp_successful_requests_total", + "Total number of successful OHTTP requests" + ) + .unwrap(), + decryption_errors_total: register_counter!( + "ohttp_decryption_errors_total", + "Total number of decryption errors" + ) + .unwrap(), + encryption_errors_total: register_counter!( + "ohttp_encryption_errors_total", + "Total number of encryption errors" + ) + .unwrap(), + backend_errors_total: register_counter!( + "ohttp_backend_errors_total", + "Total number of backend errors" + ) + .unwrap(), + key_requests_total: register_counter!( + "ohttp_key_requests_total", + "Total number of key configuration requests" + ) + .unwrap(), + request_duration: register_histogram!( + "ohttp_request_duration_seconds", + "Duration of OHTTP request processing" + ) + .unwrap(), + active_connections: register_gauge!( + "ohttp_active_connections", + "Number of active connections" + ) + .unwrap(), + } + } +} diff --git a/src/middleware/logging.rs b/src/middleware/logging.rs new file mode 100644 index 0000000..dad9974 --- /dev/null +++ b/src/middleware/logging.rs @@ -0,0 +1,56 @@ +use axum::{body::Body, extract::Request, http::StatusCode, middleware::Next, response::Response}; +use std::time::Instant; +use tracing::{info, warn, Instrument}; +use uuid::Uuid; + +pub async fn logging_middleware( + request: Request, + next: Next, +) -> Result { + let request_id = Uuid::new_v4(); + let method = request.method().clone(); + let uri = request.uri().clone(); + let user_agent = request + .headers() + .get("user-agent") + .and_then(|v| v.to_str().ok()) + .unwrap_or("unknown") + .to_string(); + + let span = tracing::info_span!( + "http_request", + request_id = %request_id, + method = %method, + uri = %uri, + user_agent = %user_agent + ); + + async move { + let start = Instant::now(); + + info!("Processing request"); + + let response = next.run(request).await; + + let duration = start.elapsed(); + let status = response.status(); + + if status.is_success() { + info!( + status = %status, + duration_ms = duration.as_millis(), + "Request completed successfully" + ); + } else { + warn!( + status = %status, + duration_ms = duration.as_millis(), + "Request failed" + ); + } + + Ok(response) + } + .instrument(span) + .await +} diff --git a/src/middleware/metrics.rs b/src/middleware/metrics.rs new file mode 100644 index 0000000..739dfef --- /dev/null +++ b/src/middleware/metrics.rs @@ -0,0 +1,17 @@ +// Additional metrics middleware if needed +use crate::state::AppState; +use axum::{body::Body, extract::Request, extract::State, middleware::Next, response::Response}; + +pub async fn metrics_middleware( + State(state): State, + request: Request, + next: Next, +) -> Response { + state.metrics.active_connections.inc(); + + let response = next.run(request).await; + + state.metrics.active_connections.dec(); + + response +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs new file mode 100644 index 0000000..2447a4b --- /dev/null +++ b/src/middleware/mod.rs @@ -0,0 +1,3 @@ +pub mod logging; +pub mod metrics; +pub mod security; diff --git a/src/middleware/security.rs b/src/middleware/security.rs new file mode 100644 index 0000000..e8df80d --- /dev/null +++ b/src/middleware/security.rs @@ -0,0 +1,188 @@ +use axum::{ + body::Body, + extract::{ConnectInfo, Request, State}, + http::{header, HeaderMap, StatusCode}, + middleware::Next, + response::{IntoResponse, Response}, +}; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::Mutex; +use tracing::{info, warn}; +use uuid::Uuid; + +use crate::{config::RateLimitConfig, state::AppState}; + +/// Rate limiter implementation +pub struct RateLimiter { + config: RateLimitConfig, + buckets: Arc>>, +} + +struct TokenBucket { + tokens: f64, + last_update: Instant, +} + +impl RateLimiter { + pub fn new(config: RateLimitConfig) -> Self { + Self { + config, + buckets: Arc::new(Mutex::new(HashMap::new())), + } + } + + pub async fn check_rate_limit(&self, key: &str) -> bool { + let mut buckets = self.buckets.lock().await; + let now = Instant::now(); + + let bucket = buckets + .entry(key.to_string()) + .or_insert_with(|| TokenBucket { + tokens: self.config.burst_size as f64, + last_update: now, + }); + + // Calculate tokens to add based on time elapsed + let elapsed = now.duration_since(bucket.last_update).as_secs_f64(); + let tokens_to_add = elapsed * (self.config.requests_per_second as f64); + + bucket.tokens = (bucket.tokens + tokens_to_add).min(self.config.burst_size as f64); + bucket.last_update = now; + + // Check if we have tokens available + if bucket.tokens >= 1.0 { + bucket.tokens -= 1.0; + true + } else { + false + } + } +} + +/// Security middleware that adds various security headers and checks +pub async fn security_middleware( + State(state): State, + ConnectInfo(addr): ConnectInfo, + request: Request, + next: Next, +) -> Result { + // Generate request ID for tracing + let request_id = Uuid::new_v4(); + + // Add security headers to the request context + let mut request = request; + request + .headers_mut() + .insert("x-request-id", request_id.to_string().parse().unwrap()); + + let is_https = matches!(request.uri().scheme_str(), Some("https")); + + // Apply rate limiting if configured + if let Some(rate_limit_config) = &state.config.rate_limit { + let rate_limiter = RateLimiter::new(rate_limit_config.clone()); + + let rate_limit_key = if rate_limit_config.by_ip { + addr.ip().to_string() + } else { + "global".to_string() + }; + + if !rate_limiter.check_rate_limit(&rate_limit_key).await { + warn!( + "Rate limit exceeded for key: {}, request_id: {}", + rate_limit_key, request_id + ); + + return Ok(( + StatusCode::TOO_MANY_REQUESTS, + [ + ( + "X-RateLimit-Limit", + rate_limit_config.requests_per_second.to_string(), + ), + ("X-RateLimit-Remaining", "0".to_string()), + ("Retry-After", "1".to_string()), + ], + "Rate limit exceeded", + ) + .into_response()); + } + } + + // Process the request + let mut response = next.run(request).await; + + // Add security headers to the response + let headers = response.headers_mut(); + + // Security headers + headers.insert("X-Content-Type-Options", "nosniff".parse().unwrap()); + headers.insert("X-Frame-Options", "DENY".parse().unwrap()); + headers.insert("X-XSS-Protection", "1; mode=block".parse().unwrap()); + headers.insert("Referrer-Policy", "no-referrer".parse().unwrap()); + headers.insert("X-Request-ID", request_id.to_string().parse().unwrap()); + + // HSTS header for HTTPS connections + if is_https { + headers.insert( + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains".parse().unwrap(), + ); + } + + // Content Security Policy + headers.insert( + "Content-Security-Policy", + "default-src 'none'; frame-ancestors 'none';" + .parse() + .unwrap(), + ); + + // Remove sensitive headers + headers.remove("Server"); + headers.remove("X-Powered-By"); + + Ok(response) +} + +/// Middleware to validate and sanitize incoming requests +pub async fn request_validation_middleware( + headers: HeaderMap, + request: Request, + next: Next, +) -> Result { + // Check for required headers only on requests with bodies + if matches!( + request.method(), + &axum::http::Method::POST | &axum::http::Method::PUT | &axum::http::Method::PATCH + ) && !headers.contains_key(header::CONTENT_TYPE) + { + return Err(StatusCode::BAD_REQUEST); + } + + // Validate User-Agent + if let Some(user_agent) = headers.get(header::USER_AGENT) { + if let Ok(ua_str) = user_agent.to_str() { + // Block known bad user agents + if ua_str.is_empty() || ua_str.contains("bot") || ua_str.contains("crawler") { + info!("Blocked suspicious user agent: {}", ua_str); + return Err(StatusCode::FORBIDDEN); + } + } + } + + // Check for suspicious headers that might indicate attacks + const SUSPICIOUS_HEADERS: &[&str] = &["x-forwarded-host", "x-original-url", "x-rewrite-url"]; + + for header_name in SUSPICIOUS_HEADERS { + if headers.contains_key(*header_name) { + warn!("Request contains suspicious header: {}", header_name); + return Err(StatusCode::BAD_REQUEST); + } + } + + Ok(next.run(request).await) +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..98a1c08 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,94 @@ +use crate::{ + config::AppConfig, + key_manager::{CipherSuiteConfig, KeyManager, KeyManagerConfig}, + metrics::AppMetrics, +}; +use std::sync::Arc; + +#[derive(Clone)] +pub struct AppState { + pub key_manager: Arc, + pub http_client: reqwest::Client, + pub config: AppConfig, + pub metrics: AppMetrics, +} + +impl AppState { + pub async fn new(config: AppConfig) -> Result> { + // Configure key manager based on app config + let key_manager_config = KeyManagerConfig { + rotation_interval: config.key_rotation_interval, + key_retention_period: config.key_retention_period, + auto_rotation_enabled: config.key_rotation_enabled, + cipher_suites: get_cipher_suites(&config), + }; + + // Initialize key manager with or without seed + let key_manager = if let Some(seed_hex) = &config.seed_secret_key { + let seed = hex::decode(seed_hex)?; + Arc::new(KeyManager::new_with_seed(key_manager_config, seed).await?) + } else { + Arc::new(KeyManager::new(key_manager_config).await?) + }; + + // Create optimized HTTP client for backend requests + let http_client = create_http_client(&config)?; + + let metrics = AppMetrics::default(); + + Ok(Self { + key_manager, + http_client, + config, + metrics, + }) + } +} + +fn get_cipher_suites(config: &AppConfig) -> Vec { + // Default cipher suites matching the Go implementation + let mut suites = vec![ + CipherSuiteConfig { + kem: "X25519_SHA256".to_string(), + kdf: "HKDF_SHA256".to_string(), + aead: "AES_128_GCM".to_string(), + }, + CipherSuiteConfig { + kem: "X25519_SHA256".to_string(), + kdf: "HKDF_SHA256".to_string(), + aead: "CHACHA20_POLY1305".to_string(), + }, + ]; + + // Add high-security suite if in production mode + if !config.debug_mode { + suites.push(CipherSuiteConfig { + kem: "P256_SHA256".to_string(), + kdf: "HKDF_SHA256".to_string(), + aead: "AES_256_GCM".to_string(), + }); + } + + suites +} + +fn create_http_client(config: &AppConfig) -> Result> { + let mut client_builder = reqwest::Client::builder() + .timeout(config.request_timeout) + .pool_max_idle_per_host(100) + .pool_idle_timeout(std::time::Duration::from_secs(30)) + .tcp_keepalive(std::time::Duration::from_secs(60)) + .tcp_nodelay(true) + .user_agent("ohttp-gateway/1.0") + .danger_accept_invalid_certs(config.debug_mode); // Only in debug mode + + // Configure proxy if needed + if let Ok(proxy_url) = std::env::var("HTTP_PROXY") { + client_builder = client_builder.proxy(reqwest::Proxy::http(proxy_url)?); + } + if let Ok(proxy_url) = std::env::var("HTTPS_PROXY") { + client_builder = client_builder.proxy(reqwest::Proxy::https(proxy_url)?); + } + + Ok(client_builder.build()?) +}