diff --git a/.changeset/tls_support.md b/.changeset/tls_support.md new file mode 100644 index 000000000..117efdff5 --- /dev/null +++ b/.changeset/tls_support.md @@ -0,0 +1,71 @@ +--- +hive-router-config: minor +hive-router-plan-executor: minor +hive-router: minor +--- + +# TLS Support + +Adds TLS support to Hive Router for both client and subgraph connections, including mutual TLS (mTLS) authentication. This allows secure communication between clients, the router, and subgraphs by encrypting data in transit and optionally verifying identities. + +## TLS Directions + +TLS Support has implementations for the following 4 directions: + +### Router -> Client - Regular TLS +Router has an `identity` (`cert`, `key`), and client has `cert`, then Client validates the router's `identity` + +### Client -> Router - mTLS +Router has the `cert`, client has the `identity`, mTLS/Client Auth then the router validates the client's `identity` + +### Subgraph -> Router - Regular TLS +Subgraph has the `identity` (`cert`, `key`), and router has `cert`, then Router validates the subgraph's `identity`. + +### Router -> Subgraph - mTLS +Subgraph has the `cert`, router(which is the client this time) has the `identity`, then subgraph validates the router's `identity`. + +## TLS Directions Diagram + +```mermaid +flowchart LR + Client["Client"] + Router["Router"] + Subgraph["Subgraph"] + + %% Router -> Client: Regular TLS + Router -- "TLS\n(cert_file + key_file)" --> Client + Client -. "validates router identity\n(cert_file)" .-> Router + + %% Client -> Router: mTLS / Client Auth + Client -- "mTLS\n(client identity)" --> Router + Router -. "validates client identity\n(client_auth.cert_file)" .-> Client + + %% Subgraph -> Router: Regular TLS + Subgraph -- "TLS\n(cert_file)" --> Router + Router -. "validates subgraph identity\n(all/subgraphs.cert_file)" .-> Subgraph + + %% Router -> Subgraph: mTLS + Router -- "mTLS\n(client_auth.cert_file + key_file)" --> Subgraph + Subgraph -. "validates router identity\n(cert_file)" .-> Router +``` + +## Configuration Structure +```yaml +traffic_shaping: + router: + key_file: # Router server private key + cert_file: # Router server certificate(s) + client_auth: # mTLS: Client -> Router + cert_file: # Trusted client CA certificate(s) + all: # Default TLS for all subgraph connections + cert_file: # Trusted subgraph CA certificate(s) + client_auth: # mTLS: Router -> Subgraph + cert_file: # Router client certificate(s) + key_file: # Router client private key + subgraphs: + SUBGRAPH_NAME: # Per-subgraph TLS override + cert_file: # Trusted subgraph CA certificate(s) + client_auth: # mTLS: Router -> Subgraph + cert_file: # Router client certificate(s) + key_file: # Router client private key +``` \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6d71b83e9..bf25c8c66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,10 +7,6 @@ name = "Inflector" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] [[package]] name = "addr2line" @@ -119,15 +115,15 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "apollo-sandbox-plugin-example" @@ -149,9 +145,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] @@ -195,6 +191,45 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom 7.1.3", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -242,24 +277,24 @@ dependencies = [ [[package]] name = "async-graphql" -version = "7.0.17" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "036618f842229ba0b89652ffe425f96c7c16a49f7e3cb23b56fca7f61fd74980" +checksum = "1057a9f7ccf2404d94571dec3451ade1cb524790df6f1ada0d19c2a49f6b0f40" dependencies = [ "async-graphql-derive", "async-graphql-parser", "async-graphql-value", - "async-stream", + "async-io", "async-trait", + "asynk-strim", "base64", "bytes", "fast_chemail", "fnv", - "futures-timer", "futures-util", "handlebars", "http", - "indexmap 2.13.0", + "indexmap 2.14.0", "mime", "multer", "num-traits", @@ -270,14 +305,14 @@ dependencies = [ "serde_urlencoded", "static_assertions_next", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "async-graphql-axum" -version = "7.0.17" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725874ecfbf399e071150b8619c4071d7b2b7a2f117e173dddef53c6bdb6bb1" +checksum = "a1e37c5532e4b686acf45e7162bc93da91fc2c702fb0d465efc2c20c8f973795" dependencies = [ "async-graphql", "axum", @@ -292,19 +327,19 @@ dependencies = [ [[package]] name = "async-graphql-derive" -version = "7.0.17" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd45deb3dbe5da5cdb8d6a670a7736d735ba65b455328440f236dfb113727a3d" +checksum = "2e6cbeadc8515e66450fba0985ce722192e28443697799988265d86304d7cc68" dependencies = [ "Inflector", "async-graphql-parser", - "darling 0.20.11", + "darling 0.23.0", "proc-macro-crate", "proc-macro2", "quote", - "strum 0.26.3", - "syn 2.0.116", - "thiserror 1.0.69", + "strum 0.27.2", + "syn 2.0.117", + "thiserror 2.0.18", ] [[package]] @@ -326,11 +361,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e3ef112905abea9dea592fc868a6873b10ebd3f983e83308f995d6284e9ba41" dependencies = [ "bytes", - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_json", ] +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + [[package]] name = "async-lock" version = "3.4.2" @@ -372,7 +425,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -389,7 +442,17 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", +] + +[[package]] +name = "asynk-strim" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6" +dependencies = [ + "futures-core", + "pin-project-lite", ] [[package]] @@ -406,9 +469,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "zeroize", @@ -416,9 +479,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "cc", "cmake", @@ -428,9 +491,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "base64", @@ -481,6 +544,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-server" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc" +dependencies = [ + "arc-swap", + "bytes", + "either", + "fs-err", + "http", + "http-body", + "hyper", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -510,9 +595,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base62" -version = "2.2.3" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adf9755786e27479693dedd3271691a92b5e242ab139cacb9fb8e7fb5381111" +checksum = "cd637ac531c60eb7fbc4684dc061c2d7d90d73d758181aa02eeff0464b9eee4b" [[package]] name = "base64" @@ -559,9 +644,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -595,9 +680,9 @@ dependencies = [ [[package]] name = "bollard" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227aa051deec8d16bd9c34605e7aaf153f240e35483dd42f6f78903847934738" +checksum = "ee04c4c84f1f811b017f2fbb7dd8815c976e7ca98593de9c1e2afad0f636bff4" dependencies = [ "base64", "bollard-stubs", @@ -644,9 +729,9 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytecount" @@ -680,9 +765,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -730,7 +815,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -758,9 +843,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -832,18 +917,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.59" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.59" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", @@ -851,9 +936,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmac" @@ -868,9 +953,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -927,9 +1012,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.19" +version = "0.15.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +checksum = "8e68cfe19cd7d23ffde002c24ffa5cda73931913ef394d5eaaa32037dc940c0c" dependencies = [ "async-trait", "convert_case 0.6.0", @@ -947,21 +1032,20 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "const-hex" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -1253,9 +1337,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" dependencies = [ "ctor-proc-macro", "dtor", @@ -1278,9 +1362,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.5.1" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", "nix", @@ -1311,7 +1395,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1326,12 +1410,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -1345,21 +1429,20 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1370,18 +1453,18 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.21.3", + "darling_core 0.23.0", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1424,16 +1507,61 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom 7.1.3", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -1452,7 +1580,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.116", + "syn 2.0.117", "unicode-xid", ] @@ -1476,11 +1604,11 @@ dependencies = [ [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -1494,7 +1622,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1554,14 +1682,14 @@ checksum = "8d1a6796ad411f6812d691955066ad27450196bfb181bb91b66a643cc3e8f5b7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "dtor" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" dependencies = [ "dtor-proc-macro", ] @@ -1591,6 +1719,7 @@ dependencies = [ "arc-swap", "async-trait", "axum", + "axum-server", "bollard", "bytes", "dashmap", @@ -1612,7 +1741,9 @@ dependencies = [ "opentelemetry-otlp", "opentelemetry-proto", "prost 0.14.3", + "rcgen", "reqwest", + "rustls", "serde", "serde_json", "sonic-rs", @@ -1726,18 +1857,18 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", ] [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "env_filter", "log", @@ -1760,7 +1891,7 @@ checksum = "452c458dfb890a2fea64e4c894f83daad61689fb9bbe84c382e1ce6d7536710b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1771,9 +1902,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ "serde", "serde_core", @@ -1842,9 +1973,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "faststr" @@ -1977,6 +2108,16 @@ dependencies = [ "num", ] +[[package]] +name = "fs-err" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" +dependencies = [ + "autocfg", + "tokio", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -2051,6 +2192,16 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.32" @@ -2059,7 +2210,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2130,21 +2281,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", - "rand_core 0.10.0", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -2181,9 +2332,9 @@ dependencies = [ [[package]] name = "grok" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e2d7bd791814b06a609b74361ac35b448eb4718548937c6de718554a4348577" +checksum = "6ddab6a9c8bb998cb2fc3101fde8ef561b7c4970db3957be7a8eee1e168f666b" dependencies = [ "glob", "onig", @@ -2212,7 +2363,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -2232,16 +2383,18 @@ dependencies = [ [[package]] name = "handlebars" -version = "5.1.2" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e" dependencies = [ + "derive_builder", "log", + "num-order", "pest", "pest_derive", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -2280,6 +2433,12 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "hashlink" version = "0.10.0" @@ -2348,7 +2507,7 @@ dependencies = [ "moka", "recloser", "regex-automata", - "regress 0.11.0", + "regress 0.11.1", "reqwest", "reqwest-middleware", "reqwest-retry 0.8.0", @@ -2514,7 +2673,7 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "indexmap 2.13.0", + "indexmap 2.14.0", "insta", "itoa", "mockito", @@ -2539,7 +2698,7 @@ dependencies = [ name = "hive-router-query-planner" version = "2.7.0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "criterion", "graphql-tools", "insta", @@ -2658,9 +2817,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -2673,7 +2832,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -2696,9 +2854,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", @@ -2706,7 +2864,6 @@ dependencies = [ "log", "rustls", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -2790,12 +2947,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -2803,9 +2961,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2816,9 +2974,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2830,15 +2988,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -2850,15 +3008,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2927,12 +3085,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -2965,7 +3123,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "inotify-sys", "libc", ] @@ -2991,9 +3149,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.46.3" +version = "1.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" dependencies = [ "console", "once_cell", @@ -3025,15 +3183,15 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -3059,9 +3217,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" @@ -3075,10 +3233,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -3249,9 +3409,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libloading" @@ -3281,15 +3441,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -3413,9 +3573,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -3440,7 +3600,7 @@ dependencies = [ "hyper-util", "log", "pin-project-lite", - "rand 0.9.2", + "rand 0.9.4", "regex", "serde_json", "serde_urlencoded", @@ -3450,9 +3610,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.13" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "async-lock", "crossbeam-channel", @@ -3514,7 +3674,7 @@ checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3525,11 +3685,11 @@ checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4" [[package]] name = "napi" -version = "3.8.3" +version = "3.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6944d0bf100571cd6e1a98a316cdca262deb6fccf8d93f5ae1502ca3fc88bd3" +checksum = "fb7848c221fb7bb789e02f01875287ebb1e078b92a6566a34de01ef8806e7c2b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "ctor", "futures", "napi-build", @@ -3549,16 +3709,16 @@ checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" [[package]] name = "napi-derive" -version = "3.5.2" +version = "3.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c914b5e420182bfb73504e0607592cdb8e2e21437d450883077669fb72a114d" +checksum = "60867ff9a6f76e82350e0c3420cb0736f5866091b61d7d8a024baa54b0ec17dd" dependencies = [ "convert_case 0.11.0", "ctor", "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3571,7 +3731,7 @@ dependencies = [ "proc-macro2", "quote", "semver", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3591,11 +3751,11 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" -version = "0.30.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -3613,12 +3773,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "nohash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" - [[package]] name = "nohash-hasher" version = "0.2.0" @@ -3668,7 +3822,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "fsevent-sys", "inotify", "kqueue", @@ -3686,7 +3840,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -3696,7 +3850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4735978b410d8496a1d89bac416af3277f6d36e9ae56d1e3977b96b81ab8048" dependencies = [ "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "derive_more", "encoding_rs", "env_logger", @@ -3759,7 +3913,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdc658d0b4caced7d7cfeefaeddd50f6c80265dc98e944beae3ac6601337b267" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "log", "ntex-codec", "ntex-io", @@ -3770,9 +3924,9 @@ dependencies = [ [[package]] name = "ntex-error" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba4b4124a2b50218182d3420428ccf56ee95c4f28a15efba1be9e97cc271d9a" +checksum = "614a4c1c3cd23c231172fe20ab42bb66e8572e8c4cf282285723fc423b64834e" dependencies = [ "backtrace", "foldhash 0.2.0", @@ -3782,11 +3936,11 @@ dependencies = [ [[package]] name = "ntex-h2" -version = "3.9.0" +version = "3.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2924795c85efb587dffe38b2d7f9295d8dfacc5cb6d00270dd96d7c34d0eb0eb" +checksum = "5e400e7ea01ad28eb530e1a34840f700718cb0e7191cfb9160554b04f464f88b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "foldhash 0.2.0", "log", "nanorand", @@ -3826,7 +3980,7 @@ version = "3.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c1d3d9a67a9abcad8981967bb1f3c59ec2c34e442771d16ed33acf663f0361" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "log", "ntex-bytes", "ntex-codec", @@ -3841,7 +3995,7 @@ version = "0.7.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81e60f4a52aae6b07b8a4c560f05802be74c10c2924838f1007f48684233aaaa" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "libc", "sc", @@ -3855,7 +4009,7 @@ checksum = "51138717dfe591b9b4063bf167ddcdc6fa8e3552157316f29f12c321493e3710" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3864,7 +4018,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83c7c631404d704766913028124c60712457b1157923d85156aaf49fbd2551e9" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "libc", "log", @@ -3988,7 +4142,7 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d799e658d04ad8be6d750b09a82fa01ad11dde264d9ec40fac873f835e87e85" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "foldhash 0.2.0", "futures-core", "futures-timer", @@ -4068,9 +4222,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -4092,6 +4246,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -4125,9 +4294,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -4167,11 +4336,20 @@ dependencies = [ "cipher", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "one-of-plugin-example" @@ -4200,7 +4378,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "once_cell", "onig_sys", @@ -4284,9 +4462,9 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" +checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" dependencies = [ "http", "opentelemetry", @@ -4371,7 +4549,7 @@ dependencies = [ "futures-util", "opentelemetry", "percent-encoding", - "rand 0.9.2", + "rand 0.9.4", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -4438,9 +4616,9 @@ dependencies = [ [[package]] name = "papaya" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f" +checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" dependencies = [ "equivalent", "seize", @@ -4573,7 +4751,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4593,7 +4771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.13.0", + "indexmap 2.14.0", ] [[package]] @@ -4604,7 +4782,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", ] @@ -4637,29 +4815,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -4690,9 +4868,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plotters" @@ -4729,6 +4907,20 @@ dependencies = [ "hive-router", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -4748,9 +4940,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -4793,7 +4985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4807,9 +4999,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -4849,13 +5041,13 @@ dependencies = [ [[package]] name = "proptest" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -4892,7 +5084,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4905,7 +5097,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4950,9 +5142,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.191" +version = "2.1.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd37f9ee74352f77f64b63295b43486c3c1790ccffed30088d45aec307704f0" +checksum = "76c0777260d32b76a8c3c197646707085d37e79d63b5872a29192c8d4f60f50b" dependencies = [ "psl-types", ] @@ -4980,7 +5172,7 @@ checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5034,7 +5226,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -5062,18 +5254,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" +checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" [[package]] name = "r-efi" @@ -5081,6 +5273,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "r2d2" version = "0.8.10" @@ -5114,9 +5312,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -5129,8 +5327,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20 0.10.0", - "getrandom 0.4.1", - "rand_core 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -5173,9 +5371,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_xorshift" @@ -5188,9 +5386,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -5206,6 +5404,20 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + [[package]] name = "recloser" version = "1.3.1" @@ -5218,9 +5430,9 @@ dependencies = [ [[package]] name = "redis" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7f6e08ce1c6a9b21684e643926f6fc3b683bc006cb89afd72a5e0eb16e3a2" +checksum = "f44e94c96d8870a387d88ce3de3fdd608cbfc0705f03cb343cdde91509d3e49a" dependencies = [ "arcstr", "combine", @@ -5250,7 +5462,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -5270,7 +5482,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5313,23 +5525,21 @@ dependencies = [ [[package]] name = "regex-filtered" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c11639076bf147be211b90e47790db89f4c22b6c8a9ca6e960833869da67166" +checksum = "ac5f7b31fbef748cc46643c1f9ba17f6d5c7c6f0ba5e372fc9c48d31ad1c8612" dependencies = [ "aho-corasick", - "indexmap 2.13.0", - "itertools 0.13.0", - "nohash", + "itertools 0.14.0", "regex", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "regress" @@ -5343,9 +5553,9 @@ dependencies = [ [[package]] name = "regress" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07948de9abc2e83adbeb7543c061a5ddaf7d944afcafbdd6e6b39aeacd40504b" +checksum = "158a764437582235e3501f683b93a0a6f8d825d04a789dbe5ed30b8799b8908a" dependencies = [ "hashbrown 0.16.1", "memchr", @@ -5483,7 +5693,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a4bd6027df676bcb752d3724db0ea3c0c5fc1dd0376fec51ac7dcaf9cc69be" dependencies = [ - "rand 0.9.2", + "rand 0.9.4", ] [[package]] @@ -5518,7 +5728,7 @@ checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" dependencies = [ "bytes", "hashbrown 0.16.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "munge", "ptr_meta", "rancor", @@ -5536,16 +5746,16 @@ checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "ron" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "once_cell", "serde", "serde_derive", @@ -5603,9 +5813,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" dependencies = [ "arrayvec", "num-traits", @@ -5619,9 +5829,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -5632,13 +5842,22 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -5647,9 +5866,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "log", @@ -5733,9 +5952,9 @@ checksum = "010e18bd3bfd1d45a7e666b236c78720df0d9a7698ebaa9c1c559961eb60a38b" [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -5796,7 +6015,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5808,7 +6027,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5845,11 +6064,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "core-foundation-sys", "libc", @@ -5858,9 +6077,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.16.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -5878,9 +6097,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -5925,7 +6144,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5936,7 +6155,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5971,28 +6190,28 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] [[package]] name = "serde_tokenstream" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" +checksum = "d7c49585c52c01f13c5c2ebb333f14f6885d76daa768d8a037d28017ec538c69" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6009,15 +6228,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -6028,14 +6247,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6044,7 +6263,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -6147,9 +6366,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simdutf8" @@ -6233,7 +6452,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6244,28 +6463,28 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "sonic-number" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a74044c092f4f43ca7a6cfd62854cf9fb5ac8502b131347c990bf22bef1dfe" +checksum = "3775c3390edf958191f1ab1e8c5c188907feebd0f3ce1604cb621f72961dbf32" dependencies = [ "cfg-if", ] [[package]] name = "sonic-rs" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4425ea8d66ec950e0a8f2ef52c766cc3d68d661d9a0845c353c40833179fd866" +checksum = "d971cc77a245ccf1756dbd1a87c3e7f709c0191464096510d43eec056d0f2c4f" dependencies = [ "ahash", "bumpalo", @@ -6274,19 +6493,19 @@ dependencies = [ "faststr", "itoa", "ref-cast", - "ryu", "serde", "simdutf8", "sonic-number", "sonic-simd", "thiserror 2.0.18", + "zmij", ] [[package]] name = "sonic-simd" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5707edbfb34a40c9f2a55fa09a49101d9fec4e0cc171ce386086bd9616f34257" +checksum = "f99e664ecd2d85a68c87e3c7a3cfe691f647ea9e835de984aba4d54a41f817d4" dependencies = [ "cfg-if", ] @@ -6348,11 +6567,11 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.26.4", + "strum_macros 0.27.2", ] [[package]] @@ -6366,15 +6585,14 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "rustversion", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6386,7 +6604,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6449,9 +6667,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -6475,7 +6693,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6496,12 +6714,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.25.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -6551,7 +6769,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6562,7 +6780,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6628,9 +6846,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -6648,9 +6866,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -6663,9 +6881,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" dependencies = [ "bytes", "libc", @@ -6680,13 +6898,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6712,9 +6930,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", @@ -6738,9 +6956,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.12+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "serde_core", "serde_spanned", @@ -6751,20 +6969,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "toml_datetime", "toml_parser", "winnow", @@ -6772,18 +6990,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "tonic" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "axum", @@ -6811,9 +7029,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" dependencies = [ "bytes", "prost 0.14.3", @@ -6828,7 +7046,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.0", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -6845,7 +7063,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http", @@ -6889,7 +7107,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -6941,9 +7159,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -6981,19 +7199,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", "http", "httparse", "log", - "rand 0.9.2", + "rand 0.9.4", "sha1", "thiserror 2.0.18", - "utf-8", ] [[package]] @@ -7019,7 +7236,7 @@ checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -7059,7 +7276,7 @@ dependencies = [ "semver", "serde", "serde_json", - "syn 2.0.116", + "syn 2.0.117", "thiserror 2.0.18", "unicode-ident", ] @@ -7077,15 +7294,15 @@ dependencies = [ "serde", "serde_json", "serde_tokenstream", - "syn 2.0.116", + "syn 2.0.117", "typify-impl", ] [[package]] name = "ua-parser" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c06b979bd5606d182759ff9cd3dda2b034b584a1ed41116407cb92abf3c995a" +checksum = "f01b7ba339f8874b643216c6c957d792047143abe848568131e5d91d2ae2ada1" dependencies = [ "regex", "regex-filtered", @@ -7104,7 +7321,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" dependencies = [ - "rand 0.9.2", + "rand 0.9.4", "web-time", ] @@ -7134,9 +7351,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -7196,12 +7413,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8-width" version = "0.1.8" @@ -7216,11 +7427,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.21.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -7249,7 +7460,7 @@ checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -7299,7 +7510,7 @@ dependencies = [ "hostname", "iana-time-zone", "idna", - "indexmap 2.13.0", + "indexmap 2.14.0", "indoc", "influxdb-line-protocol", "ipcrypt-rs", @@ -7418,9 +7629,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -7431,23 +7642,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7455,22 +7662,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -7492,7 +7699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -7518,9 +7725,9 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "semver", ] @@ -7540,9 +7747,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -7619,7 +7826,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -7630,7 +7837,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -7666,15 +7873,6 @@ 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" @@ -7824,9 +8022,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] @@ -7859,9 +8057,9 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", - "syn 2.0.116", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -7877,7 +8075,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -7889,8 +8087,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -7909,7 +8107,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "semver", "serde", @@ -7931,9 +8129,27 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom 7.1.3", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] [[package]] name = "xxhash-rust" @@ -7958,11 +8174,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -7971,54 +8196,54 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -8030,9 +8255,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -8041,9 +8266,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -8052,20 +8277,20 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "zlib-rs" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" diff --git a/bin/router/src/error.rs b/bin/router/src/error.rs index c2390cd60..03a2f2fae 100644 --- a/bin/router/src/error.rs +++ b/bin/router/src/error.rs @@ -1,4 +1,5 @@ use hive_router_config::RouterConfigError; +use hive_router_plan_executor::executors::error::TlsCertificatesError; use crate::{ jwt::jwks_manager::JwksSourceError, pipeline::usage_reporting::UsageReportingError, @@ -36,4 +37,6 @@ pub enum RouterInitError { endpoint_name_two: String, endpoint: String, }, + #[error(transparent)] + TlsCertificatesError(#[from] TlsCertificatesError), } diff --git a/bin/router/src/lib.rs b/bin/router/src/lib.rs index 3c3aca45f..c70ee7994 100644 --- a/bin/router/src/lib.rs +++ b/bin/router/src/lib.rs @@ -76,6 +76,7 @@ pub use sonic_rs; pub use tokio; pub use tracing; use tracing::{info, warn, Instrument}; +pub mod tls; #[cfg(not(feature = "graphiql"))] static LABORATORY_HTML: &str = include_str!(concat!(env!("OUT_DIR"), "/laboratory.html")); @@ -254,10 +255,11 @@ pub async fn router_entrypoint(plugin_registry: PluginRegistry) -> Result<(), Ro let paths = RouterPaths::new(graphql_path.clone(), websocket_path, callback_path); paths.detect_conflicts(&prometheus)?; + let graphql_path = graphql_path.to_string(); let long_lived_client_limit_service = LongLivedClientLimitService::new(&shared_state.router_config); - let maybe_error = web::HttpServer::new(async move || { + let server = web::HttpServer::new(async move || { let landing_page_path = graphql_path.clone(); let prometheus = prometheus.clone(); let long_lived_client_limit_service = long_lived_client_limit_service.clone(); @@ -277,9 +279,22 @@ pub async fn router_entrypoint(plugin_registry: PluginRegistry) -> Result<(), Ro .default_service(web::to(move || { landing_page_handler(landing_page_path.clone()) })) - }) - .bind(&addr) - .map_err(|err| RouterInitError::HttpServerBindError(addr, err))? + }); + + let tls_config = shared_state_clone + .router_config + .traffic_shaping + .router + .tls + .as_ref(); + + let maybe_error = if let Some(tls_config) = tls_config { + let rustls_config = tls::build_rustls_config(tls_config)?; + server.bind_rustls(&addr, &rustls_config) + } else { + server.bind(&addr) + } + .map_err(|err| RouterInitError::HttpServerBindError(addr.to_string(), err))? .run() .await .map_err(RouterInitError::HttpServerStartError); diff --git a/bin/router/src/tls.rs b/bin/router/src/tls.rs new file mode 100644 index 000000000..eb4bb036e --- /dev/null +++ b/bin/router/src/tls.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use hive_router_config::traffic_shaping::ServerTLSConfig; +use hive_router_plan_executor::executors::{ + error::TlsCertificatesError, tls::from_cert_file_config_to_certificate_der, +}; +use rustls::{ + pki_types::{pem::PemObject, PrivateKeyDer}, + server::{NoClientAuth, WebPkiClientVerifier}, + RootCertStore, ServerConfig, +}; + +pub fn build_rustls_config( + tls_config: &ServerTLSConfig, +) -> Result { + let client_auth = if let Some(client_auth_config) = tls_config.client_auth.as_ref() { + let certs = from_cert_file_config_to_certificate_der(&client_auth_config.cert_file)?; + let mut roots = RootCertStore::empty(); + roots.add_parsable_certificates(certs); + let builder = WebPkiClientVerifier::builder(roots.into()); + let required = client_auth_config.required.unwrap_or(true); + if required { + builder.build()? + } else { + builder.allow_unauthenticated().build()? + } + } else { + Arc::new(NoClientAuth) + }; + let certs = from_cert_file_config_to_certificate_der(&tls_config.cert_file)?; + let key = PrivateKeyDer::from_pem_file(&tls_config.key_file.absolute) + .map_err(|err| TlsCertificatesError::CustomTlsCertificatesError("key_file", err))?; + Ok(ServerConfig::builder() + .with_client_cert_verifier(client_auth) + .with_single_cert(certs, key)?) +} diff --git a/docs/README.md b/docs/README.md index a51ad8d32..7eddd7ab0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3152,6 +3152,7 @@ The default configuration that will be applied to all subgraphs, unless overridd |**dedupe\_enabled**|`boolean`|Enables/disables request deduplication to subgraphs.

When requests exactly matches the hashing mechanism (e.g., subgraph name, URL, headers, query, variables), and are executed at the same time, they will
be deduplicated by sharing the response of other in-flight requests.
Default: `true`
|| |**pool\_idle\_timeout**|`string`|Timeout for idle sockets being kept-alive.
Default: `"50s"`
|| |**request\_timeout**||Optional timeout configuration for requests to subgraphs.

Example with a fixed duration:
```yaml
timeout:
duration: 5s
```

Or with a VRL expression that can return a duration based on the operation kind:
```yaml
timeout:
expression: \|
if (.request.operation.type == "mutation") {
"10s"
} else {
"15s"
}
```
Default: `"30s"`
|| +|[**tls**](#traffic_shapingalltls)|`object`, `null`||| **Additional Properties:** not allowed **Example** @@ -3163,6 +3164,29 @@ request_timeout: 30s ``` + +#### traffic\_shaping\.all\.tls: object,null + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**cert\_file**|||| +|[**client\_auth**](#traffic_shapingalltlsclient_auth)|`object`, `null`||yes| +|**insecure\_skip\_ca\_verification**|`boolean`|Default: `false`
|| + +**Additional Properties:** not allowed + +##### traffic\_shaping\.all\.tls\.client\_auth: object,null + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**cert\_file**|||yes| +|**key\_file**|`string`|Format: `"path"`
|yes| + +**Additional Properties:** not allowed ### traffic\_shaping\.router: object @@ -3176,6 +3200,7 @@ Configuration for the router itself, e.g., for handling incoming requests, or ot |[**dedupe**](#traffic_shapingrouterdedupe)|`object`|Default: `{"enabled":false,"headers":"all"}`
|| |**max\_long\_lived\_clients**|`integer`|Maximum number of concurrent long-lived clients (WebSocket connections and HTTP streaming responses).
Regular non-streaming requests are not counted toward this limit.
When the limit is reached, new WebSocket and streaming HTTP requests are rejected with 503.
If both WebSockets and Subscriptions are disabled, this setting has no effect.
Default: `128`
Format: `"uint"`
Minimum: `0`
|| |**request\_timeout**|`string`|Optional timeout configuration for incoming requests to the router.
It starts from the moment the request is received by the router,
and includes the entire processing of the request (validation, execution, etc.) until a response is sent back to the client.
If a request takes longer than the specified duration, it will be aborted and a timeout error will be returned to the client.
Default: `"1m"`
|| +|[**tls**](#traffic_shapingroutertls)|`object`, `null`||yes| **Additional Properties:** not allowed **Example** @@ -3208,6 +3233,29 @@ headers: all ``` + +#### traffic\_shaping\.router\.tls: object,null + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**cert\_file**|||yes| +|[**client\_auth**](#traffic_shapingroutertlsclient_auth)|`object`, `null`||yes| +|**key\_file**|`string`|Format: `"path"`
|yes| + +**Additional Properties:** not allowed + +##### traffic\_shaping\.router\.tls\.client\_auth: object,null + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**cert\_file**|||yes| +|**required**|`boolean`, `null`||no| + +**Additional Properties:** not allowed ### traffic\_shaping\.subgraphs: object @@ -3230,6 +3278,30 @@ Optional per-subgraph configurations that will override the default configuratio |**dedupe\_enabled**|`boolean`, `null`|Enables/disables request deduplication to subgraphs.

When requests exactly matches the hashing mechanism (e.g., subgraph name, URL, headers, query, variables), and are executed at the same time, they will
be deduplicated by sharing the response of other in-flight requests.
|| |**pool\_idle\_timeout**|`string`, `null`|Timeout for idle sockets being kept-alive.
|| |**request\_timeout**||Optional timeout configuration for requests to subgraphs.

Example with a fixed duration:
```yaml
timeout:
duration: 5s
```

Or with a VRL expression that can return a duration based on the operation kind:
```yaml
timeout:
expression: \|
if (.request.operation.type == "mutation") {
"10s"
} else {
"15s"
}
```
|| +|[**tls**](#traffic_shapingsubgraphsadditionalpropertiestls)|`object`, `null`||| + +**Additional Properties:** not allowed + +##### traffic\_shaping\.subgraphs\.additionalProperties\.tls: object,null + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**cert\_file**|||| +|[**client\_auth**](#traffic_shapingsubgraphsadditionalpropertiestlsclient_auth)|`object`, `null`||yes| +|**insecure\_skip\_ca\_verification**|`boolean`|Default: `false`
|| + +**Additional Properties:** not allowed + +###### traffic\_shaping\.subgraphs\.additionalProperties\.tls\.client\_auth: object,null + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**cert\_file**|||yes| +|**key\_file**|`string`|Format: `"path"`
|yes| **Additional Properties:** not allowed diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index ef0e1ea81..be83eccdc 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -17,7 +17,7 @@ sonic-rs = { workspace = true } lazy_static = { workspace = true } jsonwebtoken = { workspace = true } insta = { workspace = true } -reqwest = { workspace = true, features = ["json"]} +reqwest = { workspace = true, features = ["json", "rustls-tls"] } opentelemetry-otlp = { workspace = true, features = ["http-proto"] } opentelemetry-proto = { workspace = true } prost = { workspace = true } @@ -31,6 +31,7 @@ dashmap = { workspace = true } axum = { workspace = true } bytes = { workspace = true } arc-swap = { workspace = true } +rustls = { workspace = true } hive-router = { path = "../bin/router", features = ["testing"] } hive-router-config = { path = "../lib/router-config" } @@ -46,4 +47,5 @@ hex = "0.4" tiny_http = "0.12" futures-util = { workspace = true } bollard = "0.20.0" - +axum-server = { version = "0.8.0", features = ["tls-rustls"] } +rcgen = "0.14.7" diff --git a/e2e/src/error_handling.rs b/e2e/src/error_handling.rs index eff2a6ecf..d609cb477 100644 --- a/e2e/src/error_handling.rs +++ b/e2e/src/error_handling.rs @@ -5,7 +5,7 @@ mod error_handling_e2e_tests { #[ntex::test] async fn should_continue_execution_when_a_subgraph_is_down() { let subgraphs = TestSubgraphs::builder().build().start().await; - let subgraphs_addr = subgraphs.addr(); + let subgraphs_url = subgraphs.url(); let router = TestRouter::builder() // we dont set subgraphs avoiding the port change in the supergrph. @@ -18,9 +18,9 @@ mod error_handling_e2e_tests { path: supergraph.graphql override_subgraph_urls: accounts: - url: "http://{subgraphs_addr}/accounts" + url: "{subgraphs_url}/accounts" reviews: - url: "http://{subgraphs_addr}/reviews" + url: "{subgraphs_url}/reviews" products: url: "http://0.0.0.0:1000/products" "#, diff --git a/e2e/src/lib.rs b/e2e/src/lib.rs index 5d7695a9f..cbbe6981f 100644 --- a/e2e/src/lib.rs +++ b/e2e/src/lib.rs @@ -55,6 +55,8 @@ mod telemetry; #[cfg(test)] mod timeout_per_subgraph; #[cfg(test)] +mod tls; +#[cfg(test)] mod websocket; pub use insta; diff --git a/e2e/src/override_subgraph_urls.rs b/e2e/src/override_subgraph_urls.rs index 079bda884..7fbabf9d1 100644 --- a/e2e/src/override_subgraph_urls.rs +++ b/e2e/src/override_subgraph_urls.rs @@ -12,7 +12,7 @@ mod override_subgraph_urls_e2e_tests { /// This way we can verify that the override is applied correctly. async fn should_override_subgraph_url_based_on_static_value() { let subgraphs = TestSubgraphs::builder().build().start().await; - let subgraphs_addr = subgraphs.addr(); + let subgraphs_url = subgraphs.url(); let router = TestRouter::builder() .inline_config(format!( @@ -22,7 +22,7 @@ mod override_subgraph_urls_e2e_tests { path: supergraph.graphql override_subgraph_urls: accounts: - url: "http://{subgraphs_addr}/accounts" + url: "{subgraphs_url}/accounts" "#, )) .build() @@ -54,7 +54,7 @@ mod override_subgraph_urls_e2e_tests { /// Without the header, the request goes to 4200 and fail (thanks to `.default`). async fn should_override_subgraph_url_based_on_header_value() { let subgraphs = TestSubgraphs::builder().build().start().await; - let subgraphs_addr = subgraphs.addr(); + let subgraphs_url = subgraphs.url(); let router = TestRouter::builder() .inline_config(format!( @@ -67,7 +67,7 @@ mod override_subgraph_urls_e2e_tests { url: expression: | if .request.headers."x-accounts-port" == "4100" {{ - "http://{subgraphs_addr}/accounts" + "{subgraphs_url}/accounts" }} else {{ .default }} diff --git a/e2e/src/testkit/mod.rs b/e2e/src/testkit/mod.rs index 475ca1721..b802b3adc 100644 --- a/e2e/src/testkit/mod.rs +++ b/e2e/src/testkit/mod.rs @@ -1,6 +1,7 @@ pub mod docker; pub mod otel; +use axum_server::{tls_rustls::RustlsConfig, Handle}; use bytes::Bytes; use dashmap::DashMap; use hive_router_plan_executor::plugin_trait::RouterPlugin; @@ -24,10 +25,7 @@ use std::{ time::{Duration, Instant}, }; use tempfile::{NamedTempFile, TempPath}; -use tokio::{ - sync::{oneshot, Semaphore}, - time, -}; +use tokio::{sync::Semaphore, time}; use tracing::{info, warn}; use hive_router::{ @@ -97,27 +95,18 @@ pub use some_header_map; /// subgraphs address and returns the modified supergraph. /// /// It will replace all occurrences of `0.0.0.0:4200` with the test subgraphs address. -pub fn supergraph_with_subgraphs( - supergraph: impl Into, - subgraphs: impl Into, -) -> String { - let original: String = supergraph.into(); - let subgraphs_addr = subgraphs.into(); - original.replace("0.0.0.0:4200", subgraphs_addr.to_string().as_str()) +pub fn supergraph_with_subgraphs(supergraph: &str, subgraphs: &str) -> String { + supergraph.replace("http://0.0.0.0:4200", subgraphs) } /// Creates a temporary supergraph file with the content of the given file but with the subgraphs /// address replaced with the test subgraphs address. /// /// The temp file will be automatically deleted when the returned TempPath is dropped. -pub fn supergraph_temp_file_with_subgraphs( - supergraph_file: &str, - subgraphs: impl Into, -) -> TempPath { +pub fn supergraph_temp_file_with_subgraphs(supergraph_file: &str, subgraphs: &str) -> TempPath { let original = std::fs::read_to_string(supergraph_file).expect("failed to read supergraph file"); - let subgraphs_addr = subgraphs.into(); - let with_addr = supergraph_with_subgraphs(original, subgraphs_addr); + let with_addr = supergraph_with_subgraphs(&original, subgraphs); let temp_file = NamedTempFile::with_suffix(".graphql").expect("failed to create temp supergraph file"); @@ -132,7 +121,7 @@ pub fn supergraph_temp_file_with_subgraphs( temp_path .to_str() .expect("failed to convert temp path to string"), - subgraphs_addr + subgraphs.to_string() ); temp_path @@ -259,6 +248,7 @@ type OnRequest = dyn Fn(RequestLike) -> Option + Send + Sync; pub struct TestSubgraphsBuilder { subscriptions_protocol: HTTPStreamingSubscriptionProtocol, on_request: Option>, + rustls_config: Option, delay: Option, } @@ -266,6 +256,7 @@ impl TestSubgraphsBuilder { pub fn new() -> Self { Self { on_request: None, + rustls_config: None, delay: None, subscriptions_protocol: HTTPStreamingSubscriptionProtocol::default(), } @@ -287,6 +278,12 @@ impl TestSubgraphsBuilder { self } + #[allow(unused)] + pub fn with_rustls_config(mut self, rustls_config: RustlsConfig) -> Self { + self.rustls_config = Some(rustls_config); + self + } + /// Adds a cooperative async delay to every subgraph request. /// Unlike `with_on_request` with `std::thread::sleep`, this yields /// back to the tokio runtime, allowing other tasks (like schema @@ -300,6 +297,7 @@ impl TestSubgraphsBuilder { pub fn build(self) -> TestSubgraphs { TestSubgraphs { on_request: self.on_request, + rustls_config: self.rustls_config, delay: self.delay, subscriptions_protocol: self.subscriptions_protocol, handle: None, @@ -315,7 +313,7 @@ impl Default for TestSubgraphsBuilder { } struct TestSubgraphsHandle { - shutdown_tx: Option>, + server_handle: Handle, addr: SocketAddr, state: Arc, } @@ -323,6 +321,7 @@ struct TestSubgraphsHandle { pub struct TestSubgraphs { subscriptions_protocol: HTTPStreamingSubscriptionProtocol, on_request: Option>, + rustls_config: Option, delay: Option, handle: Option, _state: PhantomData, @@ -404,6 +403,7 @@ impl TestSubgraphs { .await .expect("failed to bind tcp listener"); let addr = listener.local_addr().expect("failed to get local address"); + drop(listener); // release the listener; the axum_server will bind to the same addr let mut app = subgraphs_app(self.subscriptions_protocol.clone()); @@ -430,22 +430,38 @@ impl TestSubgraphs { record_requests, )); - let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); + let rustls_config_clone = self.rustls_config.clone(); + + let server_handle = Handle::new(); + let server_handle_clone = server_handle.clone(); tokio::spawn(async move { - axum::serve(listener, app) - .with_graceful_shutdown(async { - shutdown_rx.await.ok(); - }) - .await - .expect("failed to start subgraphs server"); + if let Some(rustls_config) = self.rustls_config { + axum_server::bind_rustls(addr, rustls_config) + .handle(server_handle_clone.clone()) + .serve(app.into_make_service()) + .await + .expect("failed to start subgraphs server"); + } else { + axum_server::bind(addr) + .handle(server_handle_clone.clone()) + .serve(app.into_make_service()) + .await + .expect("failed to start subgraphs server"); + } }); + let addr = server_handle + .listening() + .await + .expect("failed to get subgraphs server address"); + TestSubgraphs { on_request: self.on_request, + rustls_config: rustls_config_clone, delay: self.delay, subscriptions_protocol: self.subscriptions_protocol, handle: Some(TestSubgraphsHandle { - shutdown_tx: Some(shutdown_tx), + server_handle, addr, state: middleware_state, }), @@ -456,8 +472,14 @@ impl TestSubgraphs { impl TestSubgraphs { #[allow(unused)] - pub fn addr(&self) -> SocketAddr { - self.handle.as_ref().expect("subgraphs not started").addr + pub fn url(&self) -> String { + let addr = self.handle.as_ref().expect("subgraphs not started").addr; + let protocol = if self.rustls_config.is_some() { + "https" + } else { + "http" + }; + format!("{}://{}", protocol, addr) } /// Returns the list of requests received on the given subgraph. Supply the subgarph name. @@ -475,20 +497,14 @@ impl TestSubgraphs { /// subgraphs address and returns the modified supergraph. /// /// It will replace all occurrences of `0.0.0.0:4200` with the test subgraphs address. - pub fn supergraph(&self, supergraph: impl Into) -> String { - supergraph_with_subgraphs(supergraph, self.addr()) - } -} - -impl From<&TestSubgraphs> for SocketAddr { - fn from(subgraphs: &TestSubgraphs) -> Self { - subgraphs.addr() + pub fn supergraph(&self, supergraph: &str) -> String { + supergraph_with_subgraphs(supergraph, &self.url()) } } impl Drop for TestSubgraphsHandle { fn drop(&mut self) { - let _ = self.shutdown_tx.take().map(|tx| tx.send(())); + self.server_handle.graceful_shutdown(None); } } @@ -499,7 +515,7 @@ pub struct TestRouterBuilder { wait_for_ready_on_start: bool, config: Option, plugins: Vec PluginRegistry>>, - subgraphs_addr: Option, + subgraphs_url: Option, port: u16, listener: Option, } @@ -511,7 +527,7 @@ impl TestRouterBuilder { wait_for_ready_on_start: true, config: None, plugins: vec![], - subgraphs_addr: None, + subgraphs_url: None, port: 0, listener: None, } @@ -538,8 +554,12 @@ impl TestRouterBuilder { self } - pub fn with_subgraphs(mut self, subgraphs: impl Into) -> Self { - self.subgraphs_addr = Some(subgraphs.into()); + pub fn with_subgraphs(self, subgraphs: &TestSubgraphs) -> Self { + self.with_subgraphs_url(subgraphs.url()) + } + + pub fn with_subgraphs_url(mut self, subgraphs_url: String) -> Self { + self.subgraphs_url = Some(subgraphs_url); self } @@ -576,14 +596,14 @@ impl TestRouterBuilder { let mut _hold_until_drop: Vec> = vec![]; // change the supergraph to use the test subgraphs address - if let Some(subgraphs_addr) = self.subgraphs_addr { + if let Some(subgraphs_url) = self.subgraphs_url { match &config.supergraph { hive_router_config::supergraph::SupergraphSource::File { path, .. } => { let supergraph_path = path.as_ref().expect("supergraph file path is required"); let temp_path = supergraph_temp_file_with_subgraphs( supergraph_path.absolute.as_str(), - subgraphs_addr, + &subgraphs_url, ); let supergraph_file_path = @@ -715,7 +735,9 @@ impl TestRouter { TestRouterBuilder::new() } - pub async fn start(mut self) -> TestRouter { + // When self-signed certificates are used, the ntex test client doesn't work + // So we can't use it to call the healthcheck endpoints + pub async fn start_without_healthcheck(mut self) -> TestRouter { init_rustls_crypto_provider(); let config = self.config.take().unwrap(); let (telemetry, subscriber) = Telemetry::init_testing_subscriber(&config) @@ -804,7 +826,19 @@ impl TestRouter { let serv_paths = paths.clone(); let serv_prometheus = prometheus.clone(); let long_lived_limit = LongLivedClientLimitService::new(&shared_state.router_config); - let serv = test::server_with(test::config().listener(serv_listener), move || { + let mut serv_config = test::config().listener(serv_listener); + if let Some(tls_config) = serv_shared_state + .router_config + .traffic_shaping + .router + .tls + .as_ref() + { + let rustls_config = hive_router::tls::build_rustls_config(tls_config) + .expect("failed to build rustls config for test router"); + serv_config = serv_config.rustls(rustls_config); + } + let serv = test::server_with(serv_config, move || { let shared_state = serv_shared_state.clone(); let schema_state = serv_schema_state.clone(); let paths = serv_paths.clone(); @@ -842,7 +876,7 @@ impl TestRouter { let mut hold_until_drop = self._hold_until_drop; hold_until_drop.push(Box::new(subscription_guard)); - let started = TestRouter { + TestRouter { port: serv_port, listener: None, wait_for_healthy_on_start: self.wait_for_healthy_on_start, @@ -861,14 +895,18 @@ impl TestRouter { plugins: self.plugins, _hold_until_drop: hold_until_drop, _state: PhantomData, - }; + } + } + + pub async fn start(self) -> TestRouter { + let started = self.start_without_healthcheck().await; - if self.wait_for_healthy_on_start { + if started.wait_for_healthy_on_start { info!("Waiting for healthcheck to pass..."); started.wait_for_healthy(None).await; } - if self.wait_for_ready_on_start { + if started.wait_for_ready_on_start { info!("Waiting for readiness check to pass..."); started.wait_for_ready(None).await; } @@ -985,7 +1023,7 @@ impl TestRouter { ); let ws_url = url.as_str().replace("http://", "ws://"); let ws_uri = ws_url.parse::().expect("Failed to parse ws url"); - websocket_client::connect(&ws_uri) + websocket_client::connect(&ws_uri, None) .await .expect("Failed to connect to websocket") } diff --git a/e2e/src/tls.rs b/e2e/src/tls.rs new file mode 100644 index 000000000..ac35b246f --- /dev/null +++ b/e2e/src/tls.rs @@ -0,0 +1,844 @@ +#[cfg(test)] +mod tls_tests { + use std::{io::Write, sync::Arc}; + + use axum_server::tls_rustls::RustlsConfig; + use hive_router::init_rustls_crypto_provider; + use rcgen::generate_simple_self_signed; + use rustls::{ + pki_types::{pem::PemObject, PrivateKeyDer}, + server::WebPkiClientVerifier, + RootCertStore, ServerConfig, + }; + use sonic_rs::json; + use tempfile::NamedTempFile; + use tonic::transport::CertificateDer; + + use crate::testkit::{some_header_map, ClientResponseExt, Started, TestRouter, TestSubgraphs}; + + struct GeneratedKeyPair { + cert_file: NamedTempFile, + cert_file_path: String, + cert_pem: String, + key_file: NamedTempFile, + key_file_path: String, + key_pem: String, + } + + async fn generate_keypair() -> GeneratedKeyPair { + let cert_key = generate_simple_self_signed(vec![ + "127.0.0.1".to_string(), + "localhost".to_string(), + "0.0.0.0".to_string(), + ]) + .expect("Failed to generate self-signed certificate"); + + let mut cert_file = + NamedTempFile::new().expect("Failed to create temporary file for certificate"); + let cert = cert_key.cert; + let cert_pem = cert.pem(); + cert_file + .write(cert_pem.as_bytes()) + .expect("Failed to write certificate to temporary file"); + + let mut key_file = + NamedTempFile::new().expect("Failed to create temporary file for private key"); + + let key = cert_key.signing_key; + let key_str = key.serialize_pem(); + key_file + .write(key_str.as_bytes()) + .expect("Failed to write private key to temporary file"); + + GeneratedKeyPair { + cert_file_path: cert_file + .path() + .to_str() + .expect("Failed to convert certificate file path to string") + .to_string(), + cert_file, + cert_pem, + key_file_path: key_file + .path() + .to_str() + .expect("Failed to convert private key file path to string") + .to_string(), + key_file, + key_pem: key_str, + } + } + + // Setup TLS on router + // And send a request from a client, that has the router's certificate configured as a trusted root, to the router + // Verify that the request succeeds, indicating that TLS is working correctly on the router + #[ntex::test] + async fn router_tls() { + init_rustls_crypto_provider(); + let subgraphs = TestSubgraphs::builder().build().start().await; + let generated_key_pair = generate_keypair().await; + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + traffic_shaping: + router: + tls: + key_file: "{}" + cert_file: "{}" + + "#, + generated_key_pair.key_file_path, generated_key_pair.cert_file_path + )) + .build() + .start_without_healthcheck() + .await; + let graphql_endpoint = router.serv().url(router.graphql_path()); + let client = reqwest::Client::builder() + .add_root_certificate( + reqwest::Certificate::from_pem(generated_key_pair.cert_pem.as_bytes()) + .expect("Failed to create certificate from PEM"), + ) + .use_rustls_tls() + .build() + .expect("Failed to build reqwest client with custom TLS configuration"); + let resp = client + .post(graphql_endpoint) + .json(&json!({ + "query": "{ me { name } }" + })) + .send() + .await + .expect("Failed to send request to router with TLS"); + insta::assert_snapshot!( + resp.text().await.expect("Failed to parse text response from router with TLS") + , @r#"{"data":{"me":{"name":"Uri Goldshtein"}}}"#); + } + + async fn generate_tls_subgraph() -> (TestSubgraphs, GeneratedKeyPair) { + let generated_key_pair = generate_keypair().await; + let rustls_config = RustlsConfig::from_pem_file( + &generated_key_pair.cert_file_path, + &generated_key_pair.key_file_path, + ) + .await + .expect("Failed to create RustlsConfig from PEM files"); + let subgraphs = TestSubgraphs::builder() + .with_rustls_config(rustls_config) + .build() + .start() + .await; + (subgraphs, generated_key_pair) + } + + /// Setup TLS on a subgraph + /// Configure the router to trust the subgraph's certificate authority + /// Send a request to the router that requires communication with the TLS-enabled subgraph and verify that the request succeeds, + /// indicating that TLS is working correctly between the router and the subgraph + #[ntex::test] + async fn overriding_cert_auth_for_subgraphs() { + init_rustls_crypto_provider(); + let (subgraphs, generated_key_pair) = generate_tls_subgraph().await; + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + traffic_shaping: + subgraphs: + accounts: + tls: + cert_file: "{}" + "#, + generated_key_pair.cert_file_path + )) + .build() + .start() + .await; + let resp = router + .send_graphql_request("{ me { name } }", None, None) + .await; + assert!(resp.status().is_success(), "Expected 200 OK"); + insta::assert_snapshot!( + resp.json_body_string_pretty().await + , @r###" + { + "data": { + "me": { + "name": "Uri Goldshtein" + } + } + } + "###); + } + + // Setup two subgraph servers with TLS, each with its own certificate authority + // Configure the router to trust both certificate authorities + // Send a request to the router that requires communication with both TLS-enabled subgraphs and verify + // that the request succeeds, indicating that TLS is working correctly between the router and both subgraphs + #[ntex::test] + async fn overriding_multiple_cert_auth_for_subgraphs() { + init_rustls_crypto_provider(); + let (subgraph1, generated_key_pair1) = generate_tls_subgraph().await; + let (subgraph2, generated_key_pair2) = generate_tls_subgraph().await; + let mut combined_ca_file = + NamedTempFile::new().expect("Failed to create temporary file for certificate"); + let combined_ca_pem = format!( + "{}\n{}", + generated_key_pair1.cert_pem, generated_key_pair2.cert_pem + ); + combined_ca_file + .write(combined_ca_pem.as_bytes()) + .expect("Failed to write combined certificate to temporary file"); + let router = TestRouter::builder() + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + traffic_shaping: + all: + tls: + cert_file: "{}" + override_subgraph_urls: + accounts: + url: "{}/accounts" + reviews: + url: "{}/reviews" + "#, + combined_ca_file + .path() + .to_str() + .expect("Expected to have a path for the combined ca file"), + subgraph1.url(), + subgraph2.url() + )) + .build() + .start() + .await; + let resp = router + .send_graphql_request("{ me { name reviews { body } } }", None, None) + .await; + assert!(resp.status().is_success(), "Expected 200 OK"); + insta::assert_snapshot!( + resp.json_body_string_pretty().await + , @r#" + { + "data": { + "me": { + "name": "Uri Goldshtein", + "reviews": [ + { + "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + }, + { + "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi" + } + ] + } + } + } + "#); + } + // Setup mTLS on a subgraph + // Configure the router to communicate with the subgraph using mTLS + // Send a request to the router that requires communication with the mTLS-enabled subgraph and + // verify that the request succeeds, indicating that mTLS is working correctly between the router and the subgraph + #[ntex::test] + async fn mtls_subgraph() { + init_rustls_crypto_provider(); + let generated_keypair = generate_keypair().await; + let client_auth_generated_key_pair = generate_keypair().await; + + let mut client_auth_roots = RootCertStore::empty(); + let client_auth_cert: CertificateDer<'static> = + CertificateDer::from_pem_file(client_auth_generated_key_pair.cert_file_path) + .expect("Failed to read certificate from PEM file"); + client_auth_roots + .add(client_auth_cert.clone()) + .expect("Failed to add certificate to root store"); + + let cert = CertificateDer::from_pem_file(generated_keypair.cert_file_path) + .expect("Failed to read certificate from PEM file"); + let key: PrivateKeyDer<'static> = + PrivateKeyDer::from_pem_file(generated_keypair.key_file_path) + .expect("Failed to read private key from PEM file"); + let rustls_config = RustlsConfig::from_config(Arc::new( + ServerConfig::builder() + .with_client_cert_verifier( + WebPkiClientVerifier::builder(client_auth_roots.into()) + .build() + .expect("Failed to build WebPkiClientVerifier for mTLS test"), + ) + .with_single_cert(vec![cert], key) + .unwrap(), + )); + + let subgraphs = TestSubgraphs::builder() + .with_rustls_config(rustls_config) + .build() + .start() + .await; + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + traffic_shaping: + subgraphs: + accounts: + tls: + cert_file: "{}" + client_auth: + cert_file: "{}" + key_file: "{}" + "#, + generated_keypair + .cert_file + .path() + .to_str() + .expect("Failed to convert key file path to string"), + client_auth_generated_key_pair + .cert_file + .path() + .to_str() + .expect("Failed to convert key file path to string"), + client_auth_generated_key_pair + .key_file + .path() + .to_str() + .expect("Failed to convert key file path to string") + )) + .build() + .start() + .await; + let resp = router + .send_graphql_request("{ me { name } }", None, None) + .await; + assert!(resp.status().is_success(), "Expected 200 OK"); + insta::assert_snapshot!( + resp.json_body_string_pretty().await + , @r###" + { + "data": { + "me": { + "name": "Uri Goldshtein" + } + } + } + "###); + } + + // Setup mTLS on the router + // And setup an HTTP client with mTLS configured to communicate with the router + // Send a request to the router and verify that it succeeds, indicating that mTLS is + // working correctly on the router + #[ntex::test] + async fn mtls_router() { + init_rustls_crypto_provider(); + let subgraphs = TestSubgraphs::builder().build().start().await; + let generated_key_pair = generate_keypair().await; + let client_auth_generated_key_pair = generate_keypair().await; + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + traffic_shaping: + router: + tls: + key_file: "{}" + cert_file: "{}" + client_auth: + cert_file: "{}" + "#, + generated_key_pair.key_file_path, + generated_key_pair.cert_file_path, + client_auth_generated_key_pair.cert_file_path + )) + .build() + .start_without_healthcheck() + .await; + let graphql_endpoint = router.serv().url(router.graphql_path()); + + let mut client_auth_buf = Vec::new(); + client_auth_buf.extend_from_slice(client_auth_generated_key_pair.cert_pem.as_bytes()); + client_auth_buf.extend_from_slice(client_auth_generated_key_pair.key_pem.as_bytes()); + let identity = reqwest::Identity::from_pem(&client_auth_buf) + .expect("Failed to create identity from PEM file for mTLS test"); + + let client = reqwest::Client::builder() + .add_root_certificate( + reqwest::Certificate::from_pem(generated_key_pair.cert_pem.as_bytes()) + .expect("Failed to create certificate from PEM"), + ) + .use_rustls_tls() + .identity(identity) + .build() + .expect("Failed to build reqwest client with custom TLS configuration"); + let resp = client + .post(graphql_endpoint) + .json(&json!({ + "query": "{ me { name } }" + })) + .send() + .await + .expect("Failed to send request to router with TLS"); + insta::assert_snapshot!( + resp.text().await.expect("Failed to parse text response from router with TLS") + , @r#"{"data":{"me":{"name":"Uri Goldshtein"}}}"#); + } + + #[ntex::test] + async fn mtls_router_two_certs() { + init_rustls_crypto_provider(); + let subgraphs = TestSubgraphs::builder().build().start().await; + let generated_key_pair = generate_keypair().await; + let client_auth_generated_key_pair_1 = generate_keypair().await; + let client_auth_generated_key_pair_2 = generate_keypair().await; + let ca_contains_both_pem = format!( + "{}\n{}", + client_auth_generated_key_pair_1.cert_pem, client_auth_generated_key_pair_2.cert_pem + ); + let mut ca_file = + NamedTempFile::new().expect("Failed to create temporary file for certificate"); + ca_file + .write(ca_contains_both_pem.as_bytes()) + .expect("Failed to write combined certificate to temporary file"); + + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + traffic_shaping: + router: + tls: + key_file: "{}" + cert_file: "{}" + client_auth: + cert_file: "{}" + "#, + generated_key_pair.key_file_path, + generated_key_pair.cert_file_path, + ca_file + .path() + .to_str() + .expect("Expected to have a path for the combined ca file") + )) + .build() + .start_without_healthcheck() + .await; + let graphql_endpoint = router.serv().url(router.graphql_path()); + + async fn test_with_client_auth_pair( + graphql_endpoint: &str, + generated_key_pair: &GeneratedKeyPair, + client_auth_generated_key_pair: &GeneratedKeyPair, + ) { + let mut client_auth_buf = Vec::new(); + client_auth_buf.extend_from_slice(client_auth_generated_key_pair.cert_pem.as_bytes()); + client_auth_buf.extend_from_slice(client_auth_generated_key_pair.key_pem.as_bytes()); + let identity = reqwest::Identity::from_pem(&client_auth_buf) + .expect("Failed to create identity from PEM file for mTLS test"); + + let client = reqwest::Client::builder() + .add_root_certificate( + reqwest::Certificate::from_pem(generated_key_pair.cert_pem.as_bytes()) + .expect("Failed to create certificate from PEM"), + ) + .use_rustls_tls() + .identity(identity) + .build() + .expect("Failed to build reqwest client with custom TLS configuration"); + let resp = client + .post(graphql_endpoint) + .json(&json!({ + "query": "{ me { name } }" + })) + .send() + .await + .expect("Failed to send request to router with TLS"); + + assert_eq!(resp.status(), 200, "Expected 200 OK from router with mTLS"); + assert_eq!( + resp.text() + .await + .expect("Failed to parse text response from router with TLS"), + r#"{"data":{"me":{"name":"Uri Goldshtein"}}}"#, + "Unexpected response body from router with mTLS" + ); + } + + test_with_client_auth_pair( + &graphql_endpoint, + &generated_key_pair, + &client_auth_generated_key_pair_1, + ) + .await; + + test_with_client_auth_pair( + &graphql_endpoint, + &generated_key_pair, + &client_auth_generated_key_pair_2, + ) + .await; + } + + /// Setup TLS on a subgraph, configure the router to trust the subgraph's certificate, + /// and verify that SSE subscriptions work correctly over the TLS connection. + #[ntex::test] + async fn sse_subscription_over_tls_subgraph() { + init_rustls_crypto_provider(); + let (subgraphs, generated_key_pair) = generate_tls_subgraph().await; + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + subscriptions: + enabled: true + traffic_shaping: + all: + tls: + cert_file: "{}" + "#, + generated_key_pair.cert_file_path + )) + .build() + .start() + .await; + let resp = router + .send_graphql_request( + r#" + subscription { + reviewAdded(intervalInMs: 0) { + id + product { + name + } + } + } + "#, + None, + some_header_map!( + ntex::http::header::ACCEPT => "text/event-stream" + ), + ) + .await; + assert!(resp.status().is_success(), "Expected 200 OK"); + let body = resp.string_body().await; + assert!( + body.contains( + r#"data: {"data":{"reviewAdded":{"id":"1","product":{"name":"Table"}}}}"# + ), + "Expected at least one emitted event, got: {}", + body + ); + assert!(body.contains("event: complete")); + } + + /// Setup TLS on a subgraph, configure the router to trust the subgraph's certificate, + /// and verify that WebSocket subscriptions work correctly over the TLS (wss://) connection. + #[ntex::test] + async fn websocket_subscription_over_tls_subgraph() { + init_rustls_crypto_provider(); + let (subgraphs, generated_key_pair) = generate_tls_subgraph().await; + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + subscriptions: + enabled: true + websocket: + subgraphs: + reviews: + path: /reviews/ws + traffic_shaping: + all: + tls: + cert_file: "{}" + "#, + generated_key_pair.cert_file_path + )) + .build() + .start() + .await; + let resp = router + .send_graphql_request( + r#" + subscription { + reviewAdded(intervalInMs: 0) { + id + product { + name + } + } + } + "#, + None, + some_header_map!( + ntex::http::header::ACCEPT => "text/event-stream" + ), + ) + .await; + assert!(resp.status().is_success(), "Expected 200 OK"); + let body = resp.string_body().await; + assert!( + body.contains( + r#"data: {"data":{"reviewAdded":{"id":"1","product":{"name":"Table"}}}}"# + ), + "Expected at least one emitted event, got: {}", + body + ); + assert!(body.contains("event: complete")); + } + + /// Setup mTLS on a subgraph and verify that WebSocket subscriptions work correctly + /// when the router authenticates itself to the subgraph via client certificate. + #[ntex::test] + async fn websocket_subscription_over_mtls_subgraph() { + init_rustls_crypto_provider(); + let generated_keypair = generate_keypair().await; + let client_auth_generated_key_pair = generate_keypair().await; + + let mut client_auth_roots = RootCertStore::empty(); + let client_auth_cert: CertificateDer<'static> = + CertificateDer::from_pem_file(&client_auth_generated_key_pair.cert_file_path) + .expect("Failed to read certificate from PEM file"); + client_auth_roots + .add(client_auth_cert.clone()) + .expect("Failed to add certificate to root store"); + + let cert = CertificateDer::from_pem_file(&generated_keypair.cert_file_path) + .expect("Failed to read certificate from PEM file"); + let key: PrivateKeyDer<'static> = + PrivateKeyDer::from_pem_file(&generated_keypair.key_file_path) + .expect("Failed to read private key from PEM file"); + let rustls_config = RustlsConfig::from_config(Arc::new( + ServerConfig::builder() + .with_client_cert_verifier( + WebPkiClientVerifier::builder(client_auth_roots.into()) + .build() + .expect("Failed to build WebPkiClientVerifier for mTLS test"), + ) + .with_single_cert(vec![cert], key) + .unwrap(), + )); + + let subgraphs = TestSubgraphs::builder() + .with_rustls_config(rustls_config) + .build() + .start() + .await; + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + subscriptions: + enabled: true + websocket: + subgraphs: + reviews: + path: /reviews/ws + traffic_shaping: + all: + tls: + cert_file: "{}" + client_auth: + cert_file: "{}" + key_file: "{}" + "#, + generated_keypair + .cert_file + .path() + .to_str() + .expect("Failed to convert cert file path to string"), + client_auth_generated_key_pair + .cert_file + .path() + .to_str() + .expect("Failed to convert cert file path to string"), + client_auth_generated_key_pair + .key_file + .path() + .to_str() + .expect("Failed to convert key file path to string") + )) + .build() + .start() + .await; + let resp = router + .send_graphql_request( + r#" + subscription { + reviewAdded(intervalInMs: 0) { + id + product { + name + } + } + } + "#, + None, + some_header_map!( + ntex::http::header::ACCEPT => "text/event-stream" + ), + ) + .await; + assert!(resp.status().is_success(), "Expected 200 OK"); + let body = resp.string_body().await; + assert!( + body.contains( + r#"data: {"data":{"reviewAdded":{"id":"1","product":{"name":"Table"}}}}"# + ), + "Expected at least one emitted event, got: {}", + body + ); + assert!(body.contains("event: complete")); + } + + /// Setup TLS on a subgraph with a self-signed certificate, and verify that when + /// `insecure_skip_ca_verification` is enabled, the router successfully connects + /// without needing to trust the subgraph's CA. + #[ntex::test] + async fn insecure_skip_ca_verification() { + init_rustls_crypto_provider(); + let (subgraphs, _generated_key_pair) = generate_tls_subgraph().await; + // Note: we do NOT configure cert_file here — normally this would fail + // because the subgraph has a self-signed cert. + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config( + r#" + supergraph: + source: file + path: supergraph.graphql + traffic_shaping: + all: + tls: + insecure_skip_ca_verification: true + "# + .to_string(), + ) + .build() + .start() + .await; + let resp = router + .send_graphql_request("{ me { name } }", None, None) + .await; + assert!(resp.status().is_success(), "Expected 200 OK"); + insta::assert_snapshot!( + resp.json_body_string_pretty().await + , @r###" + { + "data": { + "me": { + "name": "Uri Goldshtein" + } + } + } + "###); + } + + /// Setup mTLS on the router with `required: false` in client_auth config. + /// Verify that clients WITHOUT a certificate can still connect successfully, + /// and clients WITH a valid certificate also connect successfully. + #[ntex::test] + async fn optional_mtls_router() { + init_rustls_crypto_provider(); + let subgraphs = TestSubgraphs::builder().build().start().await; + let generated_key_pair = generate_keypair().await; + let client_auth_generated_key_pair = generate_keypair().await; + let router = TestRouter::builder() + .with_subgraphs(&subgraphs) + .inline_config(format!( + r#" + supergraph: + source: file + path: supergraph.graphql + traffic_shaping: + router: + tls: + key_file: "{}" + cert_file: "{}" + client_auth: + cert_file: "{}" + required: false + "#, + generated_key_pair.key_file_path, + generated_key_pair.cert_file_path, + client_auth_generated_key_pair.cert_file_path + )) + .build() + .start_without_healthcheck() + .await; + let graphql_endpoint = router.serv().url(router.graphql_path()); + + // Test 1: Client WITHOUT a certificate should succeed + let client_no_cert = reqwest::Client::builder() + .add_root_certificate( + reqwest::Certificate::from_pem(generated_key_pair.cert_pem.as_bytes()) + .expect("Failed to create certificate from PEM"), + ) + .use_rustls_tls() + .build() + .expect("Failed to build reqwest client without client cert"); + let resp = client_no_cert + .post(&graphql_endpoint) + .json(&json!({ + "query": "{ me { name } }" + })) + .send() + .await + .expect("Failed to send request without client cert"); + insta::assert_snapshot!( + resp.text().await.expect("Failed to parse response") + , @r#"{"data":{"me":{"name":"Uri Goldshtein"}}}"#); + + // Test 2: Client WITH a valid certificate should also succeed + let mut client_auth_buf = Vec::new(); + client_auth_buf.extend_from_slice(client_auth_generated_key_pair.cert_pem.as_bytes()); + client_auth_buf.extend_from_slice(client_auth_generated_key_pair.key_pem.as_bytes()); + let identity = reqwest::Identity::from_pem(&client_auth_buf) + .expect("Failed to create identity from PEM"); + let client_with_cert = reqwest::Client::builder() + .add_root_certificate( + reqwest::Certificate::from_pem(generated_key_pair.cert_pem.as_bytes()) + .expect("Failed to create certificate from PEM"), + ) + .use_rustls_tls() + .identity(identity) + .build() + .expect("Failed to build reqwest client with client cert"); + let resp = client_with_cert + .post(&graphql_endpoint) + .json(&json!({ + "query": "{ me { name } }" + })) + .send() + .await + .expect("Failed to send request with client cert"); + insta::assert_snapshot!( + resp.text().await.expect("Failed to parse response") + , @r#"{"data":{"me":{"name":"Uri Goldshtein"}}}"#); + } +} diff --git a/lib/executor/src/executors/error.rs b/lib/executor/src/executors/error.rs index c1f7cc46b..148931ac9 100644 --- a/lib/executor/src/executors/error.rs +++ b/lib/executor/src/executors/error.rs @@ -1,4 +1,5 @@ use http::{uri::InvalidUri, StatusCode}; +use rustls::server::VerifierBuilderError; use strum::IntoStaticStr; #[derive(thiserror::Error, Debug, IntoStaticStr)] @@ -53,9 +54,9 @@ pub enum SubgraphExecutorError { #[error("Failed to deserialize subgraph response: {0}")] #[strum(serialize = "SUBGRAPH_RESPONSE_DESERIALIZATION_FAILURE")] ResponseDeserializationFailure(sonic_rs::Error), - #[error("Failed to initialize or load native TLS root certificates: {0}")] + #[error(transparent)] #[strum(serialize = "SUBGRAPH_HTTPS_CERTS_FAILURE")] - NativeTlsCertificatesError(std::io::Error), + TlsCertificatesError(#[from] TlsCertificatesError), #[error("Unsupported content-type '{0}': expected 'multipart/mixed' or 'text/event-stream' for HTTP subscriptions")] #[strum(serialize = "SUBGRAPH_SUBSCRIPTION_UNSUPPORTED_CONTENT_TYPE")] UnsupportedContentTypeError(String), @@ -99,3 +100,17 @@ impl SubgraphExecutorError { self.into() } } + +#[derive(thiserror::Error, Debug, IntoStaticStr)] +pub enum TlsCertificatesError { + #[error("Failed to initialize or load native TLS root certificates: {0}")] + NativeTlsCertificatesError(std::io::Error), + #[error("Failed to load custom TLS certificate {0}: {1}")] + CustomTlsCertificatesError(&'static str, rustls::pki_types::pem::Error), + #[error("Unexpected invalid certificates: {0}")] + InvalidTlsCertificates(String), + #[error("Failed to build TLS client configuration: {0}")] + TlsConfigFailure(#[from] rustls::Error), + #[error("Failed to build TLS verifier: {0}")] + TlsVerifierFailure(#[from] VerifierBuilderError), +} diff --git a/lib/executor/src/executors/map.rs b/lib/executor/src/executors/map.rs index 56eef932f..b015eb29f 100644 --- a/lib/executor/src/executors/map.rs +++ b/lib/executor/src/executors/map.rs @@ -17,9 +17,8 @@ use hive_router_internal::{ telemetry::TelemetryContext, }; use http::Uri; -use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; use hyper_util::{ - client::legacy::{connect::HttpConnector, Client}, + client::legacy::Client, rt::{TokioExecutor, TokioTimer}, }; use tokio::sync::Semaphore; @@ -31,6 +30,7 @@ use crate::{ error::SubgraphExecutorError, http::{HTTPSubgraphExecutor, HttpClient, SubgraphHttpResponse}, http_callback::{CallbackSubscriptionsMap, HttpCallbackSubgraphExecutor}, + tls::{build_https_client_config, build_https_connector, get_merged_tls_config}, websocket::WsSubgraphExecutor, }, hooks::on_subgraph_execute::{ @@ -77,14 +77,6 @@ pub struct SubgraphExecutorMap { /// Shared map of active HTTP callback subscriptions callback_subscriptions: CallbackSubscriptionsMap, } - -fn build_https_executor() -> Result, SubgraphExecutorError> { - HttpsConnectorBuilder::new() - .with_native_roots() - .map_err(SubgraphExecutorError::NativeTlsCertificatesError) - .map(|b| b.https_or_http().enable_http1().enable_http2().build()) -} - impl SubgraphExecutorMap { pub fn new( config: Arc, @@ -95,7 +87,9 @@ impl SubgraphExecutorMap { .pool_timer(TokioTimer::new()) .pool_idle_timeout(config.traffic_shaping.all.pool_idle_timeout) .pool_max_idle_per_host(config.traffic_shaping.max_connections_per_host) - .build(build_https_executor()?); + .build(build_https_connector( + config.traffic_shaping.all.tls.as_ref(), + )?); let max_connections_per_host = config.traffic_shaping.max_connections_per_host; @@ -474,10 +468,25 @@ impl SubgraphExecutorMap { ) })?; + // Resolve TLS config for the subgraph (merging global + per-subgraph) + let tls_config = get_merged_tls_config( + self.config.traffic_shaping.all.tls.as_ref(), + self.config + .traffic_shaping + .subgraphs + .get(subgraph_name) + .and_then(|s| s.tls.as_ref()), + ); + let ws_tls_config = match tls_config.as_ref() { + Some(tls) => Some(Arc::new(build_https_client_config(Some(tls))?)), + None => None, + }; + let ws_executor = WsSubgraphExecutor::new( subgraph_name.to_string(), // we use the new constructed ws_endpoint_uri here ws_endpoint_uri, + ws_tls_config, ) .to_boxed_arc(); @@ -499,10 +508,12 @@ impl SubgraphExecutorMap { let heartbeat_interval_ms = callback_config.heartbeat_interval.as_millis() as u64; + let subgraph_config = self.resolve_subgraph_config(subgraph_name)?; + let callback_executor = HttpCallbackSubgraphExecutor::new( subgraph_name.to_string(), endpoint_uri, - self.client.clone(), + subgraph_config.client, callback_config.public_url.to_string(), heartbeat_interval_ms, self.callback_subscriptions.clone(), @@ -535,18 +546,24 @@ impl SubgraphExecutorMap { return Ok(config); }; - // Override client only if pool idle timeout is customized - if let Some(pool_idle_timeout) = subgraph_config.pool_idle_timeout { - // Only override if it's different from the global setting - if pool_idle_timeout != self.config.traffic_shaping.all.pool_idle_timeout { - config.client = Arc::new( - Client::builder(TokioExecutor::new()) - .pool_timer(TokioTimer::new()) - .pool_idle_timeout(pool_idle_timeout) - .pool_max_idle_per_host(self.max_connections_per_host) - .build(build_https_executor()?), - ); - } + let pool_idle_timeout = subgraph_config + .pool_idle_timeout + .unwrap_or(self.config.traffic_shaping.all.pool_idle_timeout); + // Override client only if pool idle timeout is customized or TLS config is provided + if pool_idle_timeout != self.config.traffic_shaping.all.pool_idle_timeout + || subgraph_config.tls.is_some() + { + let tls_config = get_merged_tls_config( + self.config.traffic_shaping.all.tls.as_ref(), + subgraph_config.tls.as_ref(), + ); + config.client = Arc::new( + Client::builder(TokioExecutor::new()) + .pool_timer(TokioTimer::new()) + .pool_idle_timeout(pool_idle_timeout) + .pool_max_idle_per_host(self.max_connections_per_host) + .build(build_https_connector(tls_config.as_ref())?), + ); } // Apply other subgraph-specific overrides diff --git a/lib/executor/src/executors/mod.rs b/lib/executor/src/executors/mod.rs index 283e331e3..e7d9df22f 100644 --- a/lib/executor/src/executors/mod.rs +++ b/lib/executor/src/executors/mod.rs @@ -7,6 +7,7 @@ pub mod http_callback; pub mod map; pub mod multipart_subscribe; pub mod sse; +pub mod tls; pub mod websocket; pub mod websocket_client; pub mod websocket_common; diff --git a/lib/executor/src/executors/tls.rs b/lib/executor/src/executors/tls.rs new file mode 100644 index 000000000..7d8a4e090 --- /dev/null +++ b/lib/executor/src/executors/tls.rs @@ -0,0 +1,188 @@ +use std::sync::Arc; + +use hive_router_config::{ + primitives::{file_path::FilePath, single_or_multiple::SingleOrMultiple}, + traffic_shaping::ClientTLSConfig, +}; +use hyper_rustls::{ConfigBuilderExt, HttpsConnector, HttpsConnectorBuilder}; +use hyper_util::client::legacy::connect::HttpConnector; +use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer, ServerName, UnixTime}, + ClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme, +}; + +use crate::executors::error::{SubgraphExecutorError, TlsCertificatesError}; + +pub fn from_cert_file_config_to_certificate_der<'a>( + cert_file_path: &SingleOrMultiple, +) -> Result>, TlsCertificatesError> { + match cert_file_path { + SingleOrMultiple::Single(cert_file_path) => { + CertificateDer::pem_file_iter(&cert_file_path.absolute) + .and_then(|res| res.collect::, _>>()) + .map_err(|err| TlsCertificatesError::CustomTlsCertificatesError("cert_file", err)) + } + SingleOrMultiple::Multiple(file_paths) => file_paths + .iter() + .map(|file_path| { + CertificateDer::pem_file_iter(&file_path.absolute) + .and_then(|res| res.collect::, _>>()) + .map_err(|err| { + TlsCertificatesError::CustomTlsCertificatesError("cert_file", err) + }) + }) + .try_fold(Vec::new(), |mut acc, certs_result| { + certs_result.map(|mut certs| { + acc.append(&mut certs); + acc + }) + }), + } +} + +/// A certificate verifier that accepts any server certificate without validation. +/// Only for use in development/testing environments. +#[derive(Debug)] +struct NoVerifier; + +impl ServerCertVerifier for NoVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, + SignatureScheme::ED448, + ] + } +} + +pub fn build_https_client_config( + tls_config: Option<&ClientTLSConfig>, +) -> Result { + let insecure_skip = tls_config + .map(|c| c.insecure_skip_ca_verification) + .unwrap_or(false); + + let tls_config_for_rustls = if insecure_skip { + ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoVerifier)) + } else if let Some(cert_file_path) = tls_config.and_then(|c| c.cert_file.as_ref()) { + // Read trust roots + let certs = from_cert_file_config_to_certificate_der(cert_file_path)?; + + if certs.is_empty() { + return Err(TlsCertificatesError::InvalidTlsCertificates(format!( + "No valid certificates found in {:#?}", + cert_file_path + ))); + } + + let certs_len = certs.len(); + let mut roots = RootCertStore::empty(); + let (valid, _) = roots.add_parsable_certificates(certs); + if valid != certs_len { + return Err(TlsCertificatesError::InvalidTlsCertificates(format!( + "Expected {} certificates in {:#?}, but only {} were valid", + certs_len, cert_file_path, valid + ))); + } + // TLS client config using the custom CA store for lookups + ClientConfig::builder().with_root_certificates(roots) + } else { + ClientConfig::builder() + .with_native_roots() + .map_err(TlsCertificatesError::NativeTlsCertificatesError)? + }; + let client_config = if let Some(client_auth) = tls_config.and_then(|c| c.client_auth.as_ref()) { + let certs = from_cert_file_config_to_certificate_der(&client_auth.cert_file)?; + + let private_key = + PrivateKeyDer::from_pem_file(&client_auth.key_file.absolute).map_err(|err| { + TlsCertificatesError::CustomTlsCertificatesError("client_auth.key", err) + })?; + + tls_config_for_rustls + .with_client_auth_cert(certs, private_key) + .map_err(TlsCertificatesError::TlsConfigFailure)? + } else { + tls_config_for_rustls.with_no_client_auth() + }; + + Ok(client_config) +} + +pub fn build_https_connector( + tls_config: Option<&ClientTLSConfig>, +) -> Result, SubgraphExecutorError> { + Ok(HttpsConnectorBuilder::new() + .with_tls_config(build_https_client_config(tls_config)?) + .https_or_http() + .enable_all_versions() + .build()) +} + +pub fn get_merged_tls_config( + global: Option<&ClientTLSConfig>, + subgraph: Option<&ClientTLSConfig>, +) -> Option { + match (global, subgraph) { + (Some(global), Some(subgraph)) => { + // If both global and subgraph TLS configs are provided, we merge them by giving precedence to subgraph config values. + // If the subgraph config has a field set to None, we fall back to the global config for that field. + let merged = ClientTLSConfig { + cert_file: subgraph + .cert_file + .clone() + .or_else(|| global.cert_file.clone()), + client_auth: subgraph + .client_auth + .clone() + .or_else(|| global.client_auth.clone()), + insecure_skip_ca_verification: subgraph.insecure_skip_ca_verification + || global.insecure_skip_ca_verification, + }; + Some(merged) + } + (None, Some(subgraph)) => Some(subgraph.clone()), + (Some(global), None) => Some(global.clone()), + (None, None) => None, + } +} diff --git a/lib/executor/src/executors/websocket.rs b/lib/executor/src/executors/websocket.rs index f7fcef09b..f9754839d 100644 --- a/lib/executor/src/executors/websocket.rs +++ b/lib/executor/src/executors/websocket.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; @@ -17,13 +18,19 @@ use crate::response::subgraph_response::SubgraphResponse; pub struct WsSubgraphExecutor { subgraph_name: String, endpoint: http::Uri, + tls_config: Option>, } impl WsSubgraphExecutor { - pub fn new(subgraph_name: String, endpoint: http::Uri) -> Self { + pub fn new( + subgraph_name: String, + endpoint: http::Uri, + tls_config: Option>, + ) -> Self { Self { subgraph_name, endpoint, + tls_config, } } } @@ -42,6 +49,7 @@ impl SubgraphExecutor for WsSubgraphExecutor { ) -> Result, SubgraphExecutorError> { let endpoint = self.endpoint.clone(); let subgraph_name = self.subgraph_name.clone(); + let tls_config = self.tls_config.clone(); debug!( "establishing WebSocket connection to subgraph {} at {}", subgraph_name, endpoint @@ -60,7 +68,7 @@ impl SubgraphExecutor for WsSubgraphExecutor { // or earlier if connect/init fails. rt::spawn(async move { let result = async { - let connection = match connect(&endpoint).await { + let connection = match connect(&endpoint, tls_config).await { Ok(conn) => conn, Err(e) => { return Err(SubgraphExecutorError::WebSocketConnectFailure( @@ -120,6 +128,7 @@ impl SubgraphExecutor for WsSubgraphExecutor { let endpoint = self.endpoint.clone(); let subgraph_name = self.subgraph_name.clone(); + let tls_config = self.tls_config.clone(); let (subscribe_payload, init_payload) = build_subscribe_payload(execution_request); @@ -134,7 +143,7 @@ impl SubgraphExecutor for WsSubgraphExecutor { // this task ends when the websocket stream completes, the client drops the receiver, // or back-pressure fills the channel and we terminate the subscription. drop(rt::spawn(async move { - let connection = match connect(&endpoint).await { + let connection = match connect(&endpoint, tls_config).await { Ok(conn) => conn, Err(e) => { let _ = tx.try_send(Err(SubgraphExecutorError::WebSocketConnectFailure( diff --git a/lib/executor/src/executors/websocket_client.rs b/lib/executor/src/executors/websocket_client.rs index d17b376d3..c7dad70d6 100644 --- a/lib/executor/src/executors/websocket_client.rs +++ b/lib/executor/src/executors/websocket_client.rs @@ -86,17 +86,23 @@ impl From for SubgraphResponse<'static> { } } -pub async fn connect(uri: &http::Uri) -> Result, WsConnectError> { +pub async fn connect( + uri: &http::Uri, + custom_tls_config: Option>, +) -> Result, WsConnectError> { let scheme = uri .scheme_str() .ok_or_else(|| WsConnectError::MissingUriSchema(uri.to_string()))?; if scheme == "wss" { - let tls_config = Arc::new( - rustls::ClientConfig::builder() - .with_native_roots() - .map_err(|e| WsConnectError::NativeTlsCertificatesError(e.to_string()))? - .with_no_client_auth(), - ); + let tls_config = match custom_tls_config { + Some(config) => config, + None => Arc::new( + rustls::ClientConfig::builder() + .with_native_roots() + .map_err(|e| WsConnectError::NativeTlsCertificatesError(e.to_string()))? + .with_no_client_auth(), + ), + }; let ws_client = NtexWsClient::builder(uri) .protocols([WS_SUBPROTOCOL]) diff --git a/lib/router-config/src/traffic_shaping.rs b/lib/router-config/src/traffic_shaping.rs index 7dc22492d..348766d98 100644 --- a/lib/router-config/src/traffic_shaping.rs +++ b/lib/router-config/src/traffic_shaping.rs @@ -3,7 +3,9 @@ use std::{collections::HashMap, time::Duration}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::primitives::http_header::HttpHeaderName; +use crate::primitives::{ + file_path::FilePath, http_header::HttpHeaderName, single_or_multiple::SingleOrMultiple, +}; #[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] #[serde(deny_unknown_fields)] @@ -88,6 +90,9 @@ pub struct TrafficShapingExecutorSubgraphConfig { /// } /// ``` pub request_timeout: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub tls: Option, } #[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] @@ -129,6 +134,9 @@ pub struct TrafficShapingExecutorGlobalConfig { /// ``` #[serde(default = "default_request_timeout")] pub request_timeout: DurationOrExpression, + + #[serde(skip_serializing_if = "Option::is_none")] + pub tls: Option, } fn default_subgraph_pool_idle_timeout() -> Option { @@ -159,6 +167,7 @@ impl Default for TrafficShapingExecutorGlobalConfig { pool_idle_timeout: default_pool_idle_timeout(), dedupe_enabled: default_dedupe_enabled(), request_timeout: default_request_timeout(), + tls: None, } } } @@ -181,6 +190,9 @@ pub struct TrafficShapingRouterConfig { #[schemars(with = "String")] pub request_timeout: Duration, + #[serde(skip_serializing_if = "Option::is_none")] + pub tls: Option, + /// Maximum number of concurrent long-lived clients (WebSocket connections and HTTP streaming responses). /// Regular non-streaming requests are not counted toward this limit. /// When the limit is reached, new WebSocket and streaming HTTP requests are rejected with 503. @@ -285,7 +297,40 @@ impl Default for TrafficShapingRouterConfig { Self { dedupe: Default::default(), request_timeout: default_router_request_timeout(), + tls: None, max_long_lived_clients: default_max_long_lived_clients(), } } } + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] +#[serde(deny_unknown_fields)] +pub struct ServerTLSConfig { + pub cert_file: SingleOrMultiple, + pub key_file: FilePath, + pub client_auth: Option, +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] +#[serde(deny_unknown_fields)] +pub struct ServerClientAuthConfig { + pub cert_file: SingleOrMultiple, + #[serde(default)] + pub required: Option, +} + +#[derive(Default, Debug, Deserialize, Serialize, JsonSchema, Clone)] +#[serde(deny_unknown_fields)] +pub struct ClientTLSConfig { + pub cert_file: Option>, + pub client_auth: Option, + #[serde(default)] + pub insecure_skip_ca_verification: bool, +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)] +#[serde(deny_unknown_fields)] +pub struct ClientAuthConfig { + pub cert_file: SingleOrMultiple, + pub key_file: FilePath, +} diff --git a/plugin_examples/error_mapping/src/test.rs b/plugin_examples/error_mapping/src/test.rs index c4e8f034a..99a89cbd5 100644 --- a/plugin_examples/error_mapping/src/test.rs +++ b/plugin_examples/error_mapping/src/test.rs @@ -9,7 +9,7 @@ mod tests { let mut subgraphs = mockito::Server::new_async().await; let router = TestRouter::builder() - .with_subgraphs(subgraphs.socket_address()) + .with_subgraphs_url(format!("http://{}", subgraphs.socket_address())) .file_config("../plugin_examples/error_mapping/router.config.yaml") .register_plugin::() .build()