From 7615e47fb40cdabc971e2b8cc9e72da59b86cede Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Fri, 31 Oct 2025 17:46:28 +0300 Subject: [PATCH 1/8] feat(router): AWS Sigv4 --- Cargo.lock | 533 ++++++++++++++++++++++++++-- lib/executor/Cargo.toml | 3 + lib/executor/src/executors/error.rs | 5 + lib/executor/src/executors/http.rs | 62 +++- lib/executor/src/executors/map.rs | 1 + lib/router-config/src/aws_sig_v4.rs | 86 +++++ lib/router-config/src/lib.rs | 5 + 7 files changed, 662 insertions(+), 33 deletions(-) create mode 100644 lib/router-config/src/aws_sig_v4.rs diff --git a/Cargo.lock b/Cargo.lock index 9c47867cf..927edc899 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,7 +228,7 @@ dependencies = [ "futures-timer", "futures-util", "handlebars", - "http", + "http 1.3.1", "indexmap 2.12.0", "mime", "multer", @@ -357,6 +357,355 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-config" +version = "1.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37cf2b6af2a95a20e266782b4f76f1a5e12bf412a9db2de9c1e9123b9d8c0ad8" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.3.1", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf26925f4a5b59eb76722b63c2892b1d70d06fa053c72e4a100ec308c1d47bc" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa006bb32360ed90ac51203feafb9d02e3d21046e1fd3a450a404b90ea73e5d" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.87.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4af747ffcb5aa8da8be8f0679ef6940f1afdb8c2e10c36738c9ebeb8d17b95e" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.89.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695dc67bb861ccb8426c9129b91c30e266a0e3d85650cafdf62fcca14c8fd338" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.89.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928e87698cd916cf1efd5268148347269e6d2911028742c0061ff6261e639e3c" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffc03068fbb9c8dd5ce1c6fb240678a5cffb86fb2b7b1985c999c4b83c8df68" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.3.1", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "127fcfad33b7dfc531141fda7e1c402ac65f88aca5511a4d31e2e3d2cd01ce9c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "623254723e8dfd535f566ee7b2381645f8981da086b5c4aa26c0c41582bb1d2c" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2", + "http 1.3.1", + "hyper", + "hyper-rustls", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d1881b1ea6d313f9890710d65c158bdab6fb08c91ea825f74c1c8c357baf4cc" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d28a63441360c477465f80c7abac3b9c4d075ca638f982e605b7dc2a2c7156c9" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bbe9d018d646b96c7be063dd07987849862b0e6d07c778aad7d93d1be6c1ef0" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2fd329bf0e901ff3f60425691410c69094dc2a1f34b331f37bfc4e9ac1565a1" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "axum" version = "0.8.6" @@ -368,8 +717,8 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-util", @@ -401,8 +750,8 @@ checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -458,6 +807,26 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.108", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -542,6 +911,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "cast" version = "0.3.0" @@ -569,6 +948,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfb-mode" version = "0.8.2" @@ -692,6 +1080,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.51" @@ -743,6 +1142,15 @@ dependencies = [ "digest", ] +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "codespan-reporting" version = "0.12.0" @@ -1401,6 +1809,12 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -1758,6 +2172,12 @@ dependencies = [ "num", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.31" @@ -1952,7 +2372,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.3.1", "indexmap 2.12.0", "slab", "tokio", @@ -2061,7 +2481,7 @@ dependencies = [ "hive-router-config", "hive-router-plan-executor", "hive-router-query-planner", - "http", + "http 1.3.1", "http-body-util", "hyper", "jsonwebtoken", @@ -2095,7 +2515,7 @@ version = "0.0.11" dependencies = [ "config", "envconfig", - "http", + "http 1.3.1", "humantime-serde", "jsonwebtoken", "retry-policies", @@ -2112,6 +2532,9 @@ version = "6.0.1" dependencies = [ "ahash", "async-trait", + "aws-config", + "aws-credential-types", + "aws-sigv4", "bumpalo", "bytes", "criterion", @@ -2121,7 +2544,7 @@ dependencies = [ "graphql-tools", "hive-router-config", "hive-router-query-planner", - "http", + "http 1.3.1", "http-body-util", "hyper", "hyper-tls", @@ -2195,6 +2618,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.3.1" @@ -2206,6 +2640,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -2213,7 +2658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.3.1", ] [[package]] @@ -2224,8 +2669,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", ] @@ -2268,8 +2713,8 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -2286,7 +2731,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.3.1", "hyper", "hyper-util", "rustls", @@ -2324,8 +2769,8 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "hyper", "ipnet", "libc", @@ -2905,8 +3350,8 @@ dependencies = [ "bytes", "colored", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-util", @@ -2949,7 +3394,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.3.1", "httparse", "memchr", "mime", @@ -3218,7 +3663,7 @@ checksum = "61da3d6c8bec83c5481d7e36ed4cf1a8cd0edee3e2fa411290932b17549d5cf2" dependencies = [ "ahash", "futures-core", - "http", + "http 1.3.1", "itoa", "log", "ntex-bytes", @@ -3278,7 +3723,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb9c68c26a87ffca54339be5f95223339db3e7bcc5d64733fef20812970a746f" dependencies = [ - "http", + "http 1.3.1", "log", "ntex-bytes", "regex", @@ -4004,6 +4449,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.108", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -4383,6 +4838,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + [[package]] name = "regex-syntax" version = "0.8.8" @@ -4406,8 +4867,8 @@ dependencies = [ "encoding_rs", "futures-core", "h2", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-rustls", @@ -4447,7 +4908,7 @@ checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" dependencies = [ "anyhow", "async-trait", - "http", + "http 1.3.1", "reqwest", "serde", "thiserror 1.0.69", @@ -4464,7 +4925,7 @@ dependencies = [ "async-trait", "futures", "getrandom 0.2.16", - "http", + "http 1.3.1", "hyper", "parking_lot 0.11.2", "reqwest", @@ -4630,6 +5091,7 @@ version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -4666,6 +5128,7 @@ version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -5704,8 +6167,8 @@ dependencies = [ "bitflags 2.10.0", "bytes", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower", @@ -5827,7 +6290,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.3.1", "httparse", "log", "rand 0.9.2", @@ -5939,6 +6402,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -6578,6 +7047,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "xxhash-rust" version = "0.8.15" diff --git a/lib/executor/Cargo.toml b/lib/executor/Cargo.toml index 27f7af1bf..deeae379e 100644 --- a/lib/executor/Cargo.toml +++ b/lib/executor/Cargo.toml @@ -49,6 +49,9 @@ itoa = "1.0.15" ryu = "1.0.20" indexmap = "2.10.0" bumpalo = "3.19.0" +aws-config = "1.8.8" +aws-sigv4 = "1.3.5" +aws-credential-types = "1.2.8" [dev-dependencies] subgraphs = { path = "../../bench/subgraphs" } diff --git a/lib/executor/src/executors/error.rs b/lib/executor/src/executors/error.rs index 2234f524c..2f86c05fb 100644 --- a/lib/executor/src/executors/error.rs +++ b/lib/executor/src/executors/error.rs @@ -20,6 +20,8 @@ pub enum SubgraphExecutorError { RequestFailure(String, String), #[error("Failed to serialize variable \"{0}\": {1}")] VariablesSerializationFailure(String, String), + #[error("Failed to sign request with AWSSigV4 for subgraph \"{0}\": {1}")] + AwsSigV4SigningFailure(String, String), } impl From for GraphQLError { @@ -75,6 +77,9 @@ impl SubgraphExecutorError { SubgraphExecutorError::RequestFailure(_, _) => "SUBGRAPH_REQUEST_FAILURE", SubgraphExecutorError::VariablesSerializationFailure(_, _) => { "SUBGRAPH_VARIABLES_SERIALIZATION_FAILURE" + }, + SubgraphExecutorError::AwsSigV4SigningFailure(_, _) => { + "SUBGRAPH_AWS_SIGV4_SIGNING_FAILURE" } } } diff --git a/lib/executor/src/executors/http.rs b/lib/executor/src/executors/http.rs index 29b392567..13fa32838 100644 --- a/lib/executor/src/executors/http.rs +++ b/lib/executor/src/executors/http.rs @@ -2,12 +2,14 @@ use std::sync::Arc; use crate::executors::common::HttpExecutionResponse; use crate::executors::dedupe::{request_fingerprint, ABuildHasher, SharedResponse}; +use async_trait::async_trait; +use aws_sigv4::http_request::{ + sign, SignableBody, SignableRequest, SigningParams, +}; use dashmap::DashMap; use hive_router_config::HiveRouterConfig; use tokio::sync::OnceCell; -use async_trait::async_trait; - use bytes::{BufMut, Bytes, BytesMut}; use http::HeaderMap; use http::HeaderValue; @@ -37,6 +39,7 @@ pub struct HTTPSubgraphExecutor { pub semaphore: Arc, pub config: Arc, pub in_flight_requests: Arc>, ABuildHasher>>, + pub aws_signing_params: Option>>, } const FIRST_VARIABLE_STR: &[u8] = b",\"variables\":{"; @@ -52,6 +55,7 @@ impl HTTPSubgraphExecutor { semaphore: Arc, config: Arc, in_flight_requests: Arc>, ABuildHasher>>, + aws_signing_params: Option>>, ) -> Self { let mut header_map = HeaderMap::new(); header_map.insert( @@ -71,6 +75,7 @@ impl HTTPSubgraphExecutor { semaphore, config, in_flight_requests, + aws_signing_params, } } @@ -133,22 +138,71 @@ impl HTTPSubgraphExecutor { Ok(body) } + async fn sign_awssigv4<'a>( + &self, + req: &'a mut http::Request>, + body: &'a [u8], + ) -> Result<(), SubgraphExecutorError> { + if let Some(signing_params) = &self.aws_signing_params { + let signable_request = SignableRequest::new( + req.method().as_str(), + req.uri().to_string(), + req.headers().iter().map(|(k, v)| { + ( + k.as_str(), + str::from_utf8(v.as_bytes()) + .map_err(|err| { + SubgraphExecutorError::AwsSigV4SigningFailure( + self.subgraph_name.to_string(), + err.to_string(), + ) + }) + .unwrap(), + ) + }), + SignableBody::Bytes(body), + ) + .map_err(|err| { + SubgraphExecutorError::AwsSigV4SigningFailure( + self.subgraph_name.to_string(), + err.to_string(), + ) + })?; + + let (signing_instructions, _) = sign(signable_request, &signing_params) + .map_err(|err| { + SubgraphExecutorError::AwsSigV4SigningFailure( + self.subgraph_name.to_string(), + err.to_string(), + ) + })? + .into_parts(); + + signing_instructions.apply_to_request_http1x(req); + } + Ok(()) + } + async fn _send_request( &self, body: Vec, headers: HeaderMap, ) -> Result { - let mut req = hyper::Request::builder() + let mut req: http::Request> = hyper::Request::builder() .method(http::Method::POST) .uri(&self.endpoint) .version(Version::HTTP_11) - .body(Full::new(Bytes::from(body))) + .body(Default::default()) .map_err(|e| { SubgraphExecutorError::RequestBuildFailure(self.endpoint.to_string(), e.to_string()) })?; *req.headers_mut() = headers; + self.sign_awssigv4(&mut req, &body).await?; + + *req.body_mut() = Full::new(Bytes::from(body)); + debug!("making http request to {}", self.endpoint.to_string()); let res = self.http_client.request(req).await.map_err(|e| { diff --git a/lib/executor/src/executors/map.rs b/lib/executor/src/executors/map.rs index a3c297ad1..69c428aae 100644 --- a/lib/executor/src/executors/map.rs +++ b/lib/executor/src/executors/map.rs @@ -324,6 +324,7 @@ impl SubgraphExecutorMap { semaphore, self.config.clone(), self.in_flight_requests.clone(), + None, ); self.executors_by_subgraph diff --git a/lib/router-config/src/aws_sig_v4.rs b/lib/router-config/src/aws_sig_v4.rs new file mode 100644 index 000000000..906096d76 --- /dev/null +++ b/lib/router-config/src/aws_sig_v4.rs @@ -0,0 +1,86 @@ +use std::collections::HashMap; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct AwsSigV4Config { + /// Enables or disables AWS Signature Version 4 signing for requests to subgraphs. + /// When enabled, the router will sign requests to subgraphs using AWS SigV4. + #[serde(default = "default_aws_sig_v4_enabled")] + pub enabled: bool, + + // configuration that will apply to all subgraphs + pub all: AwsSigV4SubgraphConfig, + + // per-subgraph configuration overrides + #[serde(default)] + pub subgraphs: HashMap, +} + +impl Default for AwsSigV4Config { + fn default() -> Self { + Self { + enabled: default_aws_sig_v4_enabled(), + all: default_all_config(), + subgraphs: HashMap::new(), + } + } +} + +fn default_all_config() -> AwsSigV4SubgraphConfig { + AwsSigV4SubgraphConfig::DefaultChain(DefaultChainConfig { + profile_name: None, + region: None, + service: None, + assume_role: None, + }) +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +#[serde(untagged)] +pub enum AwsSigV4SubgraphConfig { + DefaultChain(DefaultChainConfig), + // Not recommended, prefer using default_chain as shown above + HardCoded(HardCodedConfig), +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct DefaultChainConfig { + // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#ec2-instance-profile + pub profile_name: Option, + + // https://docs.aws.amazon.com/general/latest/gr/rande.html + pub region: Option, + + // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html + pub service: Option, + + pub assume_role: Option, +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct HardCodedConfig { + pub access_key_id: String, + pub secret_access_key: String, + pub region: String, + pub service_name: String, +} + +impl AwsSigV4Config { + pub fn is_disabled(&self) -> bool { + !self.enabled + } +} + +fn default_aws_sig_v4_enabled() -> bool { + false +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct AssumeRoleConfig { + pub role_arn: String, + pub session_name: Option, + pub external_id: Option, +} \ No newline at end of file diff --git a/lib/router-config/src/lib.rs b/lib/router-config/src/lib.rs index 537244c9e..587917716 100644 --- a/lib/router-config/src/lib.rs +++ b/lib/router-config/src/lib.rs @@ -12,6 +12,7 @@ pub mod primitives; pub mod query_planner; pub mod supergraph; pub mod traffic_shaping; +pub mod aws_sig_v4; use config::{Config, File, FileFormat, FileSourceFile}; use envconfig::Envconfig; @@ -92,6 +93,10 @@ pub struct HiveRouterConfig { /// Configuration for overriding labels. #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub override_labels: OverrideLabelsConfig, + + /// Configuration for AWS SigV4 signing of requests to subgraphs. + #[serde(default, skip_serializing_if = "aws_sig_v4::AwsSigV4Config::is_disabled")] + pub aws_sig_v4: aws_sig_v4::AwsSigV4Config, } #[derive(Debug, thiserror::Error)] From e38d39fea36d6a21356f516b617c78064b3b69f4 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Thu, 6 Nov 2025 20:36:03 +0300 Subject: [PATCH 2/8] Finish impl --- Cargo.lock | 674 ++++++------------------- lib/executor/Cargo.toml | 7 +- lib/executor/src/execution/awssigv4.rs | 49 ++ lib/executor/src/execution/mod.rs | 1 + lib/executor/src/executors/error.rs | 2 +- lib/executor/src/executors/http.rs | 73 +-- lib/executor/src/executors/map.rs | 17 +- lib/router-config/src/aws_sig_v4.rs | 5 +- lib/router-config/src/lib.rs | 7 +- 9 files changed, 261 insertions(+), 574 deletions(-) create mode 100644 lib/executor/src/execution/awssigv4.rs diff --git a/Cargo.lock b/Cargo.lock index 927edc899..3f044dad6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,7 +228,7 @@ dependencies = [ "futures-timer", "futures-util", "handlebars", - "http 1.3.1", + "http", "indexmap 2.12.0", "mime", "multer", @@ -357,355 +357,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-config" -version = "1.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37cf2b6af2a95a20e266782b4f76f1a5e12bf412a9db2de9c1e9123b9d8c0ad8" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-sdk-sso", - "aws-sdk-ssooidc", - "aws-sdk-sts", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "hex", - "http 1.3.1", - "ring", - "time", - "tokio", - "tracing", - "url", - "zeroize", -] - -[[package]] -name = "aws-credential-types" -version = "1.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf26925f4a5b59eb76722b63c2892b1d70d06fa053c72e4a100ec308c1d47bc" -dependencies = [ - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "zeroize", -] - -[[package]] -name = "aws-lc-rs" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", -] - -[[package]] -name = "aws-runtime" -version = "1.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa006bb32360ed90ac51203feafb9d02e3d21046e1fd3a450a404b90ea73e5d" -dependencies = [ - "aws-credential-types", - "aws-sigv4", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http 0.2.12", - "http-body 0.4.6", - "percent-encoding", - "pin-project-lite", - "tracing", - "uuid", -] - -[[package]] -name = "aws-sdk-sso" -version = "1.87.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4af747ffcb5aa8da8be8f0679ef6940f1afdb8c2e10c36738c9ebeb8d17b95e" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http 0.2.12", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-ssooidc" -version = "1.89.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "695dc67bb861ccb8426c9129b91c30e266a0e3d85650cafdf62fcca14c8fd338" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http 0.2.12", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-sts" -version = "1.89.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928e87698cd916cf1efd5268148347269e6d2911028742c0061ff6261e639e3c" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json", - "aws-smithy-query", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-smithy-xml", - "aws-types", - "fastrand", - "http 0.2.12", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sigv4" -version = "1.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffc03068fbb9c8dd5ce1c6fb240678a5cffb86fb2b7b1985c999c4b83c8df68" -dependencies = [ - "aws-credential-types", - "aws-smithy-http", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "form_urlencoded", - "hex", - "hmac", - "http 0.2.12", - "http 1.3.1", - "percent-encoding", - "sha2", - "time", - "tracing", -] - -[[package]] -name = "aws-smithy-async" -version = "1.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "127fcfad33b7dfc531141fda7e1c402ac65f88aca5511a4d31e2e3d2cd01ce9c" -dependencies = [ - "futures-util", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "aws-smithy-http" -version = "0.62.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca" -dependencies = [ - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "futures-util", - "http 0.2.12", - "http 1.3.1", - "http-body 0.4.6", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tracing", -] - -[[package]] -name = "aws-smithy-http-client" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623254723e8dfd535f566ee7b2381645f8981da086b5c4aa26c0c41582bb1d2c" -dependencies = [ - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "h2", - "http 1.3.1", - "hyper", - "hyper-rustls", - "hyper-util", - "pin-project-lite", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower", - "tracing", -] - -[[package]] -name = "aws-smithy-json" -version = "0.61.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54" -dependencies = [ - "aws-smithy-types", -] - -[[package]] -name = "aws-smithy-observability" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1881b1ea6d313f9890710d65c158bdab6fb08c91ea825f74c1c8c357baf4cc" -dependencies = [ - "aws-smithy-runtime-api", -] - -[[package]] -name = "aws-smithy-query" -version = "0.60.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d28a63441360c477465f80c7abac3b9c4d075ca638f982e605b7dc2a2c7156c9" -dependencies = [ - "aws-smithy-types", - "urlencoding", -] - -[[package]] -name = "aws-smithy-runtime" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbe9d018d646b96c7be063dd07987849862b0e6d07c778aad7d93d1be6c1ef0" -dependencies = [ - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-http-client", - "aws-smithy-observability", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "fastrand", - "http 0.2.12", - "http 1.3.1", - "http-body 0.4.6", - "http-body 1.0.1", - "pin-project-lite", - "pin-utils", - "tokio", - "tracing", -] - -[[package]] -name = "aws-smithy-runtime-api" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193" -dependencies = [ - "aws-smithy-async", - "aws-smithy-types", - "bytes", - "http 0.2.12", - "http 1.3.1", - "pin-project-lite", - "tokio", - "tracing", - "zeroize", -] - -[[package]] -name = "aws-smithy-types" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e" -dependencies = [ - "base64-simd", - "bytes", - "bytes-utils", - "http 0.2.12", - "http 1.3.1", - "http-body 0.4.6", - "http-body 1.0.1", - "http-body-util", - "itoa", - "num-integer", - "pin-project-lite", - "pin-utils", - "ryu", - "serde", - "time", -] - -[[package]] -name = "aws-smithy-xml" -version = "0.60.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56" -dependencies = [ - "xmlparser", -] - -[[package]] -name = "aws-types" -version = "1.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2fd329bf0e901ff3f60425691410c69094dc2a1f34b331f37bfc4e9ac1565a1" -dependencies = [ - "aws-credential-types", - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "rustc_version", - "tracing", -] - [[package]] name = "axum" version = "0.8.6" @@ -717,8 +368,8 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "hyper", "hyper-util", @@ -750,8 +401,8 @@ checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" dependencies = [ "bytes", "futures-core", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -807,26 +458,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.10.0", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.108", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -911,16 +542,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bytes-utils" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" -dependencies = [ - "bytes", - "either", -] - [[package]] name = "cast" version = "0.3.0" @@ -948,15 +569,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", -] - [[package]] name = "cfb-mode" version = "0.8.2" @@ -1080,17 +692,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.51" @@ -1142,15 +743,6 @@ dependencies = [ "digest", ] -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] - [[package]] name = "codespan-reporting" version = "0.12.0" @@ -2172,12 +1764,6 @@ dependencies = [ "num", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futures" version = "0.3.31" @@ -2372,7 +1958,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.3.1", + "http", "indexmap 2.12.0", "slab", "tokio", @@ -2481,7 +2067,7 @@ dependencies = [ "hive-router-config", "hive-router-plan-executor", "hive-router-query-planner", - "http 1.3.1", + "http", "http-body-util", "hyper", "jsonwebtoken", @@ -2515,7 +2101,7 @@ version = "0.0.11" dependencies = [ "config", "envconfig", - "http 1.3.1", + "http", "humantime-serde", "jsonwebtoken", "retry-policies", @@ -2532,9 +2118,6 @@ version = "6.0.1" dependencies = [ "ahash", "async-trait", - "aws-config", - "aws-credential-types", - "aws-sigv4", "bumpalo", "bytes", "criterion", @@ -2544,7 +2127,7 @@ dependencies = [ "graphql-tools", "hive-router-config", "hive-router-query-planner", - "http 1.3.1", + "http", "http-body-util", "hyper", "hyper-tls", @@ -2555,6 +2138,10 @@ dependencies = [ "ntex-http", "ordered-float", "regex-automata", + "reqsign-aws-v4", + "reqsign-core", + "reqsign-file-read-tokio", + "reqsign-http-send-reqwest", "ryu", "serde", "sonic-rs", @@ -2618,17 +2205,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.3.1" @@ -2640,17 +2216,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -2658,7 +2223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http", ] [[package]] @@ -2669,8 +2234,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -2713,8 +2278,8 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -2731,7 +2296,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.3.1", + "http", "hyper", "hyper-util", "rustls", @@ -2769,8 +2334,8 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "hyper", "ipnet", "libc", @@ -3049,6 +2614,47 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -3190,16 +2796,6 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link 0.2.1", -] - [[package]] name = "libm" version = "0.2.15" @@ -3350,8 +2946,8 @@ dependencies = [ "bytes", "colored", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "hyper", "hyper-util", @@ -3394,7 +2990,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.3.1", + "http", "httparse", "memchr", "mime", @@ -3663,7 +3259,7 @@ checksum = "61da3d6c8bec83c5481d7e36ed4cf1a8cd0edee3e2fa411290932b17549d5cf2" dependencies = [ "ahash", "futures-core", - "http 1.3.1", + "http", "itoa", "log", "ntex-bytes", @@ -3723,7 +3319,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb9c68c26a87ffca54339be5f95223339db3e7bcc5d64733fef20812970a746f" dependencies = [ - "http 1.3.1", + "http", "log", "ntex-bytes", "regex", @@ -4419,6 +4015,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -4449,16 +4054,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.108", -] - [[package]] name = "primeorder" version = "0.13.6" @@ -4585,6 +4180,16 @@ dependencies = [ "tracing-tree", ] +[[package]] +name = "quick-xml" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quinn" version = "0.11.9" @@ -4838,12 +4443,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "regex-lite" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" - [[package]] name = "regex-syntax" version = "0.8.8" @@ -4856,6 +4455,79 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6" +[[package]] +name = "reqsign-aws-v4" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4510c2a3e42b653cf788d560a3d54b0ae4cc315a62aaba773554f18319c0db0b" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "form_urlencoded", + "http", + "log", + "percent-encoding", + "quick-xml", + "reqsign-core", + "rust-ini", + "serde", + "serde_json", + "serde_urlencoded", + "sha1", +] + +[[package]] +name = "reqsign-core" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39da118ccf3bdb067ac6cc40136fec99bc5ba418cbd388dc88e4ce0e5d0b1423" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http", + "jiff", + "log", + "percent-encoding", + "sha1", + "sha2", + "windows-sys 0.61.2", +] + +[[package]] +name = "reqsign-file-read-tokio" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669ea66036266a9ac371d2e63cc7d345e69994da0168b4e6f3487fe21e126f76" +dependencies = [ + "anyhow", + "async-trait", + "reqsign-core", + "tokio", +] + +[[package]] +name = "reqsign-http-send-reqwest" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46186bce769674f9200ad01af6f2ca42de3e819ddc002fff1edae135bfb6cd9c" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "futures-channel", + "http", + "http-body-util", + "reqsign-core", + "reqwest", + "wasm-bindgen-futures", +] + [[package]] name = "reqwest" version = "0.12.24" @@ -4867,8 +4539,8 @@ dependencies = [ "encoding_rs", "futures-core", "h2", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "hyper", "hyper-rustls", @@ -4908,7 +4580,7 @@ checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" dependencies = [ "anyhow", "async-trait", - "http 1.3.1", + "http", "reqwest", "serde", "thiserror 1.0.69", @@ -4925,7 +4597,7 @@ dependencies = [ "async-trait", "futures", "getrandom 0.2.16", - "http 1.3.1", + "http", "hyper", "parking_lot 0.11.2", "reqwest", @@ -5091,7 +4763,6 @@ version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ - "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -5128,7 +4799,6 @@ version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -6167,8 +5837,8 @@ dependencies = [ "bitflags 2.10.0", "bytes", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "iri-string", "pin-project-lite", "tower", @@ -6290,7 +5960,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.3.1", + "http", "httparse", "log", "rand 0.9.2", @@ -6402,12 +6072,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf-8" version = "0.7.6" @@ -7047,12 +6711,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" -[[package]] -name = "xmlparser" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" - [[package]] name = "xxhash-rust" version = "0.8.15" diff --git a/lib/executor/Cargo.toml b/lib/executor/Cargo.toml index deeae379e..7666dfb83 100644 --- a/lib/executor/Cargo.toml +++ b/lib/executor/Cargo.toml @@ -49,9 +49,10 @@ itoa = "1.0.15" ryu = "1.0.20" indexmap = "2.10.0" bumpalo = "3.19.0" -aws-config = "1.8.8" -aws-sigv4 = "1.3.5" -aws-credential-types = "1.2.8" +reqsign-aws-v4 = "2.0.1" +reqsign-core = "2.0.1" +reqsign-file-read-tokio = "2.0.1" +reqsign-http-send-reqwest = "2.0.1" [dev-dependencies] subgraphs = { path = "../../bench/subgraphs" } diff --git a/lib/executor/src/execution/awssigv4.rs b/lib/executor/src/execution/awssigv4.rs new file mode 100644 index 000000000..12098f02d --- /dev/null +++ b/lib/executor/src/execution/awssigv4.rs @@ -0,0 +1,49 @@ +use hive_router_config::aws_sig_v4::AwsSigV4SubgraphConfig; +use reqsign_aws_v4::{ + Credential, DefaultCredentialProvider, ProfileCredentialProvider, RequestSigner, + StaticCredentialProvider, +}; +use reqsign_core::{Context, OsEnv, ProvideCredentialChain, Signer}; +use reqsign_file_read_tokio::TokioFileRead; +use reqsign_http_send_reqwest::ReqwestHttpSend; + +pub fn create_awssigv4_signer(config: &AwsSigV4SubgraphConfig) -> Signer { + let ctx = Context::new() + .with_file_read(TokioFileRead) + .with_http_send(ReqwestHttpSend::default()) + .with_env(OsEnv); + let mut loader = ProvideCredentialChain::new(); + match config { + AwsSigV4SubgraphConfig::DefaultChain(default_chain) => { + loader = loader.push(DefaultCredentialProvider::new()); + if let Some(profile_name) = &default_chain.profile_name { + loader = loader.push(ProfileCredentialProvider::new().with_profile(profile_name)); + } + } + AwsSigV4SubgraphConfig::HardCoded(hard_coded) => { + let mut provider = StaticCredentialProvider::new( + &hard_coded.access_key_id, + &hard_coded.secret_access_key, + ); + if let Some(session_token) = &hard_coded.session_token { + provider = provider.with_session_token(session_token); + } + loader = loader.push(provider); + } + } + let service: &str = match config { + AwsSigV4SubgraphConfig::DefaultChain(default_chain) => { + default_chain.service.as_ref().map_or("s3", |v| v) + } + AwsSigV4SubgraphConfig::HardCoded(hard_coded) => hard_coded.service_name.as_str(), + }; + let region: &str = match config { + AwsSigV4SubgraphConfig::DefaultChain(default_chain) => { + default_chain.region.as_ref().map_or("us-east-1", |v| v) + } + AwsSigV4SubgraphConfig::HardCoded(hard_coded) => hard_coded.region.as_str(), + }; + let builder = RequestSigner::new(service, region); + + Signer::new(ctx, loader, builder) +} diff --git a/lib/executor/src/execution/mod.rs b/lib/executor/src/execution/mod.rs index 52dc59506..26a28c572 100644 --- a/lib/executor/src/execution/mod.rs +++ b/lib/executor/src/execution/mod.rs @@ -1,3 +1,4 @@ +pub mod awssigv4; pub mod client_request_details; pub mod error; pub mod jwt_forward; diff --git a/lib/executor/src/executors/error.rs b/lib/executor/src/executors/error.rs index 2f86c05fb..ecc6d5df0 100644 --- a/lib/executor/src/executors/error.rs +++ b/lib/executor/src/executors/error.rs @@ -77,7 +77,7 @@ impl SubgraphExecutorError { SubgraphExecutorError::RequestFailure(_, _) => "SUBGRAPH_REQUEST_FAILURE", SubgraphExecutorError::VariablesSerializationFailure(_, _) => { "SUBGRAPH_VARIABLES_SERIALIZATION_FAILURE" - }, + } SubgraphExecutorError::AwsSigV4SigningFailure(_, _) => { "SUBGRAPH_AWS_SIGV4_SIGNING_FAILURE" } diff --git a/lib/executor/src/executors/http.rs b/lib/executor/src/executors/http.rs index 13fa32838..2d45929f4 100644 --- a/lib/executor/src/executors/http.rs +++ b/lib/executor/src/executors/http.rs @@ -3,11 +3,10 @@ use std::sync::Arc; use crate::executors::common::HttpExecutionResponse; use crate::executors::dedupe::{request_fingerprint, ABuildHasher, SharedResponse}; use async_trait::async_trait; -use aws_sigv4::http_request::{ - sign, SignableBody, SignableRequest, SigningParams, -}; use dashmap::DashMap; use hive_router_config::HiveRouterConfig; +use reqsign_aws_v4::Credential; +use reqsign_core::Signer; use tokio::sync::OnceCell; use bytes::{BufMut, Bytes, BytesMut}; @@ -39,7 +38,7 @@ pub struct HTTPSubgraphExecutor { pub semaphore: Arc, pub config: Arc, pub in_flight_requests: Arc>, ABuildHasher>>, - pub aws_signing_params: Option>>, + pub aws_sigv4_signer: Option>, } const FIRST_VARIABLE_STR: &[u8] = b",\"variables\":{"; @@ -55,7 +54,7 @@ impl HTTPSubgraphExecutor { semaphore: Arc, config: Arc, in_flight_requests: Arc>, ABuildHasher>>, - aws_signing_params: Option>>, + aws_sigv4_signer: Option>, ) -> Self { let mut header_map = HeaderMap::new(); header_map.insert( @@ -75,7 +74,7 @@ impl HTTPSubgraphExecutor { semaphore, config, in_flight_requests, - aws_signing_params, + aws_sigv4_signer, } } @@ -138,51 +137,6 @@ impl HTTPSubgraphExecutor { Ok(body) } - async fn sign_awssigv4<'a>( - &self, - req: &'a mut http::Request>, - body: &'a [u8], - ) -> Result<(), SubgraphExecutorError> { - if let Some(signing_params) = &self.aws_signing_params { - let signable_request = SignableRequest::new( - req.method().as_str(), - req.uri().to_string(), - req.headers().iter().map(|(k, v)| { - ( - k.as_str(), - str::from_utf8(v.as_bytes()) - .map_err(|err| { - SubgraphExecutorError::AwsSigV4SigningFailure( - self.subgraph_name.to_string(), - err.to_string(), - ) - }) - .unwrap(), - ) - }), - SignableBody::Bytes(body), - ) - .map_err(|err| { - SubgraphExecutorError::AwsSigV4SigningFailure( - self.subgraph_name.to_string(), - err.to_string(), - ) - })?; - - let (signing_instructions, _) = sign(signable_request, &signing_params) - .map_err(|err| { - SubgraphExecutorError::AwsSigV4SigningFailure( - self.subgraph_name.to_string(), - err.to_string(), - ) - })? - .into_parts(); - - signing_instructions.apply_to_request_http1x(req); - } - Ok(()) - } - async fn _send_request( &self, body: Vec, @@ -192,19 +146,26 @@ impl HTTPSubgraphExecutor { .method(http::Method::POST) .uri(&self.endpoint) .version(Version::HTTP_11) - .body(Default::default()) + .body(Full::new(Bytes::from(body))) .map_err(|e| { SubgraphExecutorError::RequestBuildFailure(self.endpoint.to_string(), e.to_string()) })?; *req.headers_mut() = headers; - self.sign_awssigv4(&mut req, &body).await?; - - *req.body_mut() = Full::new(Bytes::from(body)); - debug!("making http request to {}", self.endpoint.to_string()); + if let Some(aws_sigv4_signer) = &self.aws_sigv4_signer { + let (mut parts, body) = req.into_parts(); + aws_sigv4_signer.sign(&mut parts, None).await.map_err(|e| { + SubgraphExecutorError::AwsSigV4SigningFailure( + self.endpoint.to_string(), + e.to_string(), + ) + })?; + req = http::Request::from_parts(parts, body); + } + let res = self.http_client.request(req).await.map_err(|e| { SubgraphExecutorError::RequestFailure(self.endpoint.to_string(), e.to_string()) })?; diff --git a/lib/executor/src/executors/map.rs b/lib/executor/src/executors/map.rs index 69c428aae..63b0429a5 100644 --- a/lib/executor/src/executors/map.rs +++ b/lib/executor/src/executors/map.rs @@ -27,7 +27,7 @@ use vrl::{ }; use crate::{ - execution::client_request_details::ClientRequestDetails, + execution::{awssigv4::create_awssigv4_signer, client_request_details::ClientRequestDetails}, executors::{ common::{ HttpExecutionRequest, HttpExecutionResponse, SubgraphExecutor, SubgraphExecutorBoxedArc, @@ -317,6 +317,19 @@ impl SubgraphExecutorMap { .or_insert_with(|| Arc::new(Semaphore::new(self.max_connections_per_host))) .clone(); + let aws_sigv4_signer = if self.config.aws_sig_v4.is_disabled() { + None + } else { + let aws_sigv4_subgraph_config = self + .config + .aws_sig_v4 + .subgraphs + .get(subgraph_name) + .unwrap_or(&self.config.aws_sig_v4.all); + + Some(create_awssigv4_signer(aws_sigv4_subgraph_config)) + }; + let executor = HTTPSubgraphExecutor::new( subgraph_name.to_string(), endpoint_uri, @@ -324,7 +337,7 @@ impl SubgraphExecutorMap { semaphore, self.config.clone(), self.in_flight_requests.clone(), - None, + aws_sigv4_signer, ); self.executors_by_subgraph diff --git a/lib/router-config/src/aws_sig_v4.rs b/lib/router-config/src/aws_sig_v4.rs index 906096d76..594b328c3 100644 --- a/lib/router-config/src/aws_sig_v4.rs +++ b/lib/router-config/src/aws_sig_v4.rs @@ -13,7 +13,7 @@ pub struct AwsSigV4Config { // configuration that will apply to all subgraphs pub all: AwsSigV4SubgraphConfig, - + // per-subgraph configuration overrides #[serde(default)] pub subgraphs: HashMap, @@ -66,6 +66,7 @@ pub struct HardCodedConfig { pub secret_access_key: String, pub region: String, pub service_name: String, + pub session_token: Option, } impl AwsSigV4Config { @@ -83,4 +84,4 @@ pub struct AssumeRoleConfig { pub role_arn: String, pub session_name: Option, pub external_id: Option, -} \ No newline at end of file +} diff --git a/lib/router-config/src/lib.rs b/lib/router-config/src/lib.rs index 587917716..0476f7e9d 100644 --- a/lib/router-config/src/lib.rs +++ b/lib/router-config/src/lib.rs @@ -1,3 +1,4 @@ +pub mod aws_sig_v4; pub mod cors; pub mod csrf; mod env_overrides; @@ -12,7 +13,6 @@ pub mod primitives; pub mod query_planner; pub mod supergraph; pub mod traffic_shaping; -pub mod aws_sig_v4; use config::{Config, File, FileFormat, FileSourceFile}; use envconfig::Envconfig; @@ -95,7 +95,10 @@ pub struct HiveRouterConfig { pub override_labels: OverrideLabelsConfig, /// Configuration for AWS SigV4 signing of requests to subgraphs. - #[serde(default, skip_serializing_if = "aws_sig_v4::AwsSigV4Config::is_disabled")] + #[serde( + default, + skip_serializing_if = "aws_sig_v4::AwsSigV4Config::is_disabled" + )] pub aws_sig_v4: aws_sig_v4::AwsSigV4Config, } From 9f56229a1efe8cc142b0980286ac4a519f6029a9 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Thu, 6 Nov 2025 20:38:57 +0300 Subject: [PATCH 3/8] Update docs --- docs/README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/README.md b/docs/README.md index fb474b9d2..d70977165 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,7 @@ |Name|Type|Description|Required| |----|----|-----------|--------| +|[**aws\_sig\_v4**](#aws_sig_v4)|`object`|Configuration for AWS SigV4 signing of requests to subgraphs.
|yes| |[**cors**](#cors)|`object`|Configuration for CORS (Cross-Origin Resource Sharing).
Default: `{"allow_any_origin":false,"allow_credentials":false,"enabled":false,"policies":[]}`
|yes| |[**csrf**](#csrf)|`object`|Configuration for CSRF prevention.
Default: `{"enabled":false,"required_headers":[]}`
|| |[**graphiql**](#graphiql)|`object`|Configuration for the GraphiQL interface.
Default: `{"enabled":true}`
|| @@ -21,6 +22,9 @@ **Example** ```yaml +aws_sig_v4: + enabled: false + subgraphs: {} cors: allow_any_origin: false allow_credentials: false @@ -113,6 +117,38 @@ traffic_shaping: ``` + +## aws\_sig\_v4: object + +Configuration for AWS SigV4 signing of requests to subgraphs. + + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**all**|||yes| +|**enabled**|`boolean`|Enables or disables AWS Signature Version 4 signing for requests to subgraphs.
When enabled, the router will sign requests to subgraphs using AWS SigV4.
Default: `false`
|no| +|[**subgraphs**](#aws_sig_v4subgraphs)|`object`|Default: `{}`
|no| + +**Additional Properties:** not allowed +**Example** + +```yaml +enabled: false +subgraphs: {} + +``` + + +### aws\_sig\_v4\.subgraphs: object + +**Additional Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**Additional Properties**|||| + ## cors: object From f177e22ed836740d540d4e7fac97654adf9902fa Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Thu, 6 Nov 2025 20:41:05 +0300 Subject: [PATCH 4/8] Lockfile --- Cargo.lock | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f044dad6..40287d55c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1401,12 +1401,6 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "dyn-clone" version = "1.0.20" @@ -2796,6 +2790,16 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libm" version = "0.2.15" From 94353d39db9c9f6c6bf4a607da2eb52ed6ac4666 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Thu, 6 Nov 2025 21:44:30 +0300 Subject: [PATCH 5/8] Region --- lib/executor/src/execution/awssigv4.rs | 16 ++++++++++++++-- lib/router-config/src/aws_sig_v4.rs | 1 - 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/executor/src/execution/awssigv4.rs b/lib/executor/src/execution/awssigv4.rs index 12098f02d..f37ba6b6c 100644 --- a/lib/executor/src/execution/awssigv4.rs +++ b/lib/executor/src/execution/awssigv4.rs @@ -1,7 +1,7 @@ use hive_router_config::aws_sig_v4::AwsSigV4SubgraphConfig; use reqsign_aws_v4::{ - Credential, DefaultCredentialProvider, ProfileCredentialProvider, RequestSigner, - StaticCredentialProvider, + AssumeRoleWithWebIdentityCredentialProvider, Credential, DefaultCredentialProvider, + ProfileCredentialProvider, RequestSigner, StaticCredentialProvider, }; use reqsign_core::{Context, OsEnv, ProvideCredentialChain, Signer}; use reqsign_file_read_tokio::TokioFileRead; @@ -19,6 +19,18 @@ pub fn create_awssigv4_signer(config: &AwsSigV4SubgraphConfig) -> Signer { let mut provider = StaticCredentialProvider::new( diff --git a/lib/router-config/src/aws_sig_v4.rs b/lib/router-config/src/aws_sig_v4.rs index 594b328c3..8ff85e59a 100644 --- a/lib/router-config/src/aws_sig_v4.rs +++ b/lib/router-config/src/aws_sig_v4.rs @@ -83,5 +83,4 @@ fn default_aws_sig_v4_enabled() -> bool { pub struct AssumeRoleConfig { pub role_arn: String, pub session_name: Option, - pub external_id: Option, } From e95072682c4a9b66f7b12f1efbb4d14a86400247 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Fri, 7 Nov 2025 02:25:56 +0300 Subject: [PATCH 6/8] More --- lib/executor/src/execution/awssigv4.rs | 38 +++++++++++++++----------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/lib/executor/src/execution/awssigv4.rs b/lib/executor/src/execution/awssigv4.rs index f37ba6b6c..aaba656b5 100644 --- a/lib/executor/src/execution/awssigv4.rs +++ b/lib/executor/src/execution/awssigv4.rs @@ -1,7 +1,7 @@ use hive_router_config::aws_sig_v4::AwsSigV4SubgraphConfig; use reqsign_aws_v4::{ - AssumeRoleWithWebIdentityCredentialProvider, Credential, DefaultCredentialProvider, - ProfileCredentialProvider, RequestSigner, StaticCredentialProvider, + Credential, DefaultCredentialProvider, DefaultCredentialProviderBuilder, RequestSigner, + StaticCredentialProvider, }; use reqsign_core::{Context, OsEnv, ProvideCredentialChain, Signer}; use reqsign_file_read_tokio::TokioFileRead; @@ -14,22 +14,28 @@ pub fn create_awssigv4_signer(config: &AwsSigV4SubgraphConfig) -> Signer { + AwsSigV4SubgraphConfig::DefaultChain(default_chain_config) => { loader = loader.push(DefaultCredentialProvider::new()); - if let Some(profile_name) = &default_chain.profile_name { - loader = loader.push(ProfileCredentialProvider::new().with_profile(profile_name)); + let mut default_chain_builder = DefaultCredentialProviderBuilder::new(); + if let Some(profile_name) = &default_chain_config.profile_name { + default_chain_builder = default_chain_builder + .configure_profile(|p| p.with_credentials_file(profile_name)); } - if let Some(assume_role) = &default_chain.assume_role { - let mut assume_role_provider = AssumeRoleWithWebIdentityCredentialProvider::new() - .with_role_arn(assume_role.role_arn.to_string()); - if let Some(session_name) = &assume_role.session_name { - assume_role_provider = - assume_role_provider.with_role_session_name(session_name.to_string()); - } - if let Some(region) = &default_chain.region { - assume_role_provider = assume_role_provider.with_region(region.to_string()); - } - loader = loader.push(assume_role_provider); + if let Some(assume_role_config) = &default_chain_config.assume_role { + default_chain_builder = + default_chain_builder.configure_assume_role(|mut assume_role| { + assume_role = assume_role.with_role_arn(&assume_role_config.role_arn); + if let Some(session_name) = &assume_role_config.session_name { + assume_role = + assume_role.with_role_session_name(session_name.to_string()); + } + if let Some(region) = &default_chain_config.region { + assume_role = assume_role.with_region(region.to_string()); + } + assume_role + }); + let default_chain = default_chain_builder.build(); + loader = loader.push(default_chain); } } AwsSigV4SubgraphConfig::HardCoded(hard_coded) => { From 70d9a3e0d132c949ea6ca743db3fc0b3131dea7a Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Fri, 7 Nov 2025 16:57:56 +0300 Subject: [PATCH 7/8] Tests --- Cargo.lock | 1 + lib/executor/Cargo.toml | 1 + lib/executor/src/execution/awssigv4.rs | 102 +++++++++++++++++++++---- lib/executor/src/executors/map.rs | 18 ++--- lib/router-config/src/aws_sig_v4.rs | 24 ++---- 5 files changed, 102 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40287d55c..b19e48ebf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2114,6 +2114,7 @@ dependencies = [ "async-trait", "bumpalo", "bytes", + "chrono", "criterion", "dashmap", "futures", diff --git a/lib/executor/Cargo.toml b/lib/executor/Cargo.toml index 7666dfb83..55c689f95 100644 --- a/lib/executor/Cargo.toml +++ b/lib/executor/Cargo.toml @@ -59,6 +59,7 @@ subgraphs = { path = "../../bench/subgraphs" } criterion = { workspace = true } tokio = { workspace = true } insta = { workspace = true } +chrono = "0.4.42" [[bench]] name = "executor_benches" diff --git a/lib/executor/src/execution/awssigv4.rs b/lib/executor/src/execution/awssigv4.rs index aaba656b5..945e52b2c 100644 --- a/lib/executor/src/execution/awssigv4.rs +++ b/lib/executor/src/execution/awssigv4.rs @@ -7,13 +7,16 @@ use reqsign_core::{Context, OsEnv, ProvideCredentialChain, Signer}; use reqsign_file_read_tokio::TokioFileRead; use reqsign_http_send_reqwest::ReqwestHttpSend; -pub fn create_awssigv4_signer(config: &AwsSigV4SubgraphConfig) -> Signer { +pub fn create_awssigv4_signer(config: &AwsSigV4SubgraphConfig) -> Option> { let ctx = Context::new() .with_file_read(TokioFileRead) .with_http_send(ReqwestHttpSend::default()) .with_env(OsEnv); let mut loader = ProvideCredentialChain::new(); match config { + AwsSigV4SubgraphConfig::Disabled => { + return None; + } AwsSigV4SubgraphConfig::DefaultChain(default_chain_config) => { loader = loader.push(DefaultCredentialProvider::new()); let mut default_chain_builder = DefaultCredentialProviderBuilder::new(); @@ -24,14 +27,13 @@ pub fn create_awssigv4_signer(config: &AwsSigV4SubgraphConfig) -> Signer Signer { - default_chain.service.as_ref().map_or("s3", |v| v) - } - AwsSigV4SubgraphConfig::HardCoded(hard_coded) => hard_coded.service_name.as_str(), + AwsSigV4SubgraphConfig::DefaultChain(default_chain) => &default_chain.service, + AwsSigV4SubgraphConfig::HardCoded(hard_coded) => &hard_coded.service_name, + AwsSigV4SubgraphConfig::Disabled => unreachable!(), }; let region: &str = match config { - AwsSigV4SubgraphConfig::DefaultChain(default_chain) => { - default_chain.region.as_ref().map_or("us-east-1", |v| v) - } - AwsSigV4SubgraphConfig::HardCoded(hard_coded) => hard_coded.region.as_str(), + AwsSigV4SubgraphConfig::DefaultChain(default_chain) => &default_chain.region, + AwsSigV4SubgraphConfig::HardCoded(hard_coded) => &hard_coded.region, + AwsSigV4SubgraphConfig::Disabled => unreachable!(), }; let builder = RequestSigner::new(service, region); - Signer::new(ctx, loader, builder) + Some(Signer::new(ctx, loader, builder)) +} + +#[cfg(test)] +mod tests { + use crate::execution::awssigv4::create_awssigv4_signer; + use bytes::Bytes; + use chrono::Utc; + use hive_router_config::aws_sig_v4::{AwsSigV4SubgraphConfig, HardCodedConfig}; + use http_body_util::Full; + use hyper::body::Body; + + #[tokio::test] + async fn signs_the_request_correctly() { + let access_key_id = "AKIAIOSFODNN7EXAMPLE"; + let secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; + let region = "eu-central-1"; + let service_name = "s3"; + let config = AwsSigV4SubgraphConfig::HardCoded(HardCodedConfig { + access_key_id: access_key_id.to_string(), + secret_access_key: secret_access_key.to_string(), + region: region.to_string(), + service_name: service_name.to_string(), + session_token: None, + }); + let signer = create_awssigv4_signer(&config).expect("Expected to return a signer"); + let body = Full::new(Bytes::from("query { hello }")); + let content_length = body.size_hint().exact().unwrap(); + let endpoint = format!( + "http://sigv4examplegraphqlbucket.{}-{}.amazonaws.com", + service_name, region + ); + let req: http::Request> = http::Request::builder() + .method("POST") + .uri(endpoint) + .header("Accept", "application/json") + .header("Content-Length", content_length) + .header("Content-Type", "application/json") + .body(body) + .unwrap(); + + let (mut parts, body) = req.into_parts(); + + signer + .sign(&mut parts, None) + .await + .expect("Expected to sign correctly"); + + let req = http::Request::from_parts(parts, body); + + let authorization_header = req + .headers() + .get("Authorization") + .expect("Expected to have Authorization header") + .to_str() + .expect("Expected to convert to str"); + + let date_stamp = Utc::now().format("%Y%m%d"); + + let mut expected_auth_header_prefix = "AWS4-HMAC-SHA256 ".to_string(); + expected_auth_header_prefix.push_str(&format!( + "Credential={}/{}/{}/{}/aws4_request, ", + access_key_id, date_stamp, region, service_name + )); + expected_auth_header_prefix.push_str( + "SignedHeaders=accept;content-length;content-type;host;x-amz-content-sha256;x-amz-date, Signature=", + ); + + assert!( + authorization_header.starts_with(&expected_auth_header_prefix), + "Expected authorization header to start with '{}', but got '{}'", + expected_auth_header_prefix, + authorization_header + ); + } } diff --git a/lib/executor/src/executors/map.rs b/lib/executor/src/executors/map.rs index 63b0429a5..9fc1746b3 100644 --- a/lib/executor/src/executors/map.rs +++ b/lib/executor/src/executors/map.rs @@ -317,18 +317,14 @@ impl SubgraphExecutorMap { .or_insert_with(|| Arc::new(Semaphore::new(self.max_connections_per_host))) .clone(); - let aws_sigv4_signer = if self.config.aws_sig_v4.is_disabled() { - None - } else { - let aws_sigv4_subgraph_config = self - .config - .aws_sig_v4 - .subgraphs - .get(subgraph_name) - .unwrap_or(&self.config.aws_sig_v4.all); + let aws_sigv4_subgraph_config = self + .config + .aws_sig_v4 + .subgraphs + .get(subgraph_name) + .unwrap_or(&self.config.aws_sig_v4.all); - Some(create_awssigv4_signer(aws_sigv4_subgraph_config)) - }; + let aws_sigv4_signer = create_awssigv4_signer(aws_sigv4_subgraph_config); let executor = HTTPSubgraphExecutor::new( subgraph_name.to_string(), diff --git a/lib/router-config/src/aws_sig_v4.rs b/lib/router-config/src/aws_sig_v4.rs index 8ff85e59a..15ab3d978 100644 --- a/lib/router-config/src/aws_sig_v4.rs +++ b/lib/router-config/src/aws_sig_v4.rs @@ -6,11 +6,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct AwsSigV4Config { - /// Enables or disables AWS Signature Version 4 signing for requests to subgraphs. - /// When enabled, the router will sign requests to subgraphs using AWS SigV4. - #[serde(default = "default_aws_sig_v4_enabled")] - pub enabled: bool, - // configuration that will apply to all subgraphs pub all: AwsSigV4SubgraphConfig, @@ -22,7 +17,6 @@ pub struct AwsSigV4Config { impl Default for AwsSigV4Config { fn default() -> Self { Self { - enabled: default_aws_sig_v4_enabled(), all: default_all_config(), subgraphs: HashMap::new(), } @@ -30,17 +24,13 @@ impl Default for AwsSigV4Config { } fn default_all_config() -> AwsSigV4SubgraphConfig { - AwsSigV4SubgraphConfig::DefaultChain(DefaultChainConfig { - profile_name: None, - region: None, - service: None, - assume_role: None, - }) + AwsSigV4SubgraphConfig::Disabled } #[derive(Debug, Deserialize, Serialize, JsonSchema)] #[serde(untagged)] pub enum AwsSigV4SubgraphConfig { + Disabled, DefaultChain(DefaultChainConfig), // Not recommended, prefer using default_chain as shown above HardCoded(HardCodedConfig), @@ -52,10 +42,10 @@ pub struct DefaultChainConfig { pub profile_name: Option, // https://docs.aws.amazon.com/general/latest/gr/rande.html - pub region: Option, + pub region: String, // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html - pub service: Option, + pub service: String, pub assume_role: Option, } @@ -71,14 +61,10 @@ pub struct HardCodedConfig { impl AwsSigV4Config { pub fn is_disabled(&self) -> bool { - !self.enabled + matches!(self.all, AwsSigV4SubgraphConfig::Disabled) && self.subgraphs.is_empty() } } -fn default_aws_sig_v4_enabled() -> bool { - false -} - #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct AssumeRoleConfig { pub role_arn: String, From 972307f8f6f92a3e416e816c03d81fffe004d1d6 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Fri, 7 Nov 2025 17:05:44 +0300 Subject: [PATCH 8/8] Lets go --- docs/README.md | 5 +--- lib/executor/src/execution/awssigv4.rs | 36 ++++++++++++++------------ lib/router-config/src/aws_sig_v4.rs | 13 +++++++--- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/docs/README.md b/docs/README.md index d70977165..6cf190532 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,6 @@ ```yaml aws_sig_v4: - enabled: false subgraphs: {} cors: allow_any_origin: false @@ -128,14 +127,12 @@ Configuration for AWS SigV4 signing of requests to subgraphs. |Name|Type|Description|Required| |----|----|-----------|--------| |**all**|||yes| -|**enabled**|`boolean`|Enables or disables AWS Signature Version 4 signing for requests to subgraphs.
When enabled, the router will sign requests to subgraphs using AWS SigV4.
Default: `false`
|no| -|[**subgraphs**](#aws_sig_v4subgraphs)|`object`|Default: `{}`
|no| +|[**subgraphs**](#aws_sig_v4subgraphs)|`object`||no| **Additional Properties:** not allowed **Example** ```yaml -enabled: false subgraphs: {} ``` diff --git a/lib/executor/src/execution/awssigv4.rs b/lib/executor/src/execution/awssigv4.rs index 945e52b2c..ad07711e5 100644 --- a/lib/executor/src/execution/awssigv4.rs +++ b/lib/executor/src/execution/awssigv4.rs @@ -17,7 +17,9 @@ pub fn create_awssigv4_signer(config: &AwsSigV4SubgraphConfig) -> Option { return None; } - AwsSigV4SubgraphConfig::DefaultChain(default_chain_config) => { + AwsSigV4SubgraphConfig::DefaultChain { + default_chain: default_chain_config, + } => { loader = loader.push(DefaultCredentialProvider::new()); let mut default_chain_builder = DefaultCredentialProviderBuilder::new(); if let Some(profile_name) = &default_chain_config.profile_name { @@ -40,25 +42,25 @@ pub fn create_awssigv4_signer(config: &AwsSigV4SubgraphConfig) -> Option { + AwsSigV4SubgraphConfig::HardCoded { hardcoded } => { let mut provider = StaticCredentialProvider::new( - &hard_coded.access_key_id, - &hard_coded.secret_access_key, + &hardcoded.access_key_id, + &hardcoded.secret_access_key, ); - if let Some(session_token) = &hard_coded.session_token { + if let Some(session_token) = &hardcoded.session_token { provider = provider.with_session_token(session_token); } loader = loader.push(provider); } } let service: &str = match config { - AwsSigV4SubgraphConfig::DefaultChain(default_chain) => &default_chain.service, - AwsSigV4SubgraphConfig::HardCoded(hard_coded) => &hard_coded.service_name, + AwsSigV4SubgraphConfig::DefaultChain { default_chain } => &default_chain.service, + AwsSigV4SubgraphConfig::HardCoded { hardcoded } => &hardcoded.service_name, AwsSigV4SubgraphConfig::Disabled => unreachable!(), }; let region: &str = match config { - AwsSigV4SubgraphConfig::DefaultChain(default_chain) => &default_chain.region, - AwsSigV4SubgraphConfig::HardCoded(hard_coded) => &hard_coded.region, + AwsSigV4SubgraphConfig::DefaultChain { default_chain } => &default_chain.region, + AwsSigV4SubgraphConfig::HardCoded { hardcoded } => &hardcoded.region, AwsSigV4SubgraphConfig::Disabled => unreachable!(), }; let builder = RequestSigner::new(service, region); @@ -81,13 +83,15 @@ mod tests { let secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; let region = "eu-central-1"; let service_name = "s3"; - let config = AwsSigV4SubgraphConfig::HardCoded(HardCodedConfig { - access_key_id: access_key_id.to_string(), - secret_access_key: secret_access_key.to_string(), - region: region.to_string(), - service_name: service_name.to_string(), - session_token: None, - }); + let config = AwsSigV4SubgraphConfig::HardCoded { + hardcoded: HardCodedConfig { + access_key_id: access_key_id.to_string(), + secret_access_key: secret_access_key.to_string(), + region: region.to_string(), + service_name: service_name.to_string(), + session_token: None, + }, + }; let signer = create_awssigv4_signer(&config).expect("Expected to return a signer"); let body = Full::new(Bytes::from("query { hello }")); let content_length = body.size_hint().exact().unwrap(); diff --git a/lib/router-config/src/aws_sig_v4.rs b/lib/router-config/src/aws_sig_v4.rs index 15ab3d978..a1d095f18 100644 --- a/lib/router-config/src/aws_sig_v4.rs +++ b/lib/router-config/src/aws_sig_v4.rs @@ -10,7 +10,7 @@ pub struct AwsSigV4Config { pub all: AwsSigV4SubgraphConfig, // per-subgraph configuration overrides - #[serde(default)] + #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub subgraphs: HashMap, } @@ -31,9 +31,9 @@ fn default_all_config() -> AwsSigV4SubgraphConfig { #[serde(untagged)] pub enum AwsSigV4SubgraphConfig { Disabled, - DefaultChain(DefaultChainConfig), + DefaultChain { default_chain: DefaultChainConfig }, // Not recommended, prefer using default_chain as shown above - HardCoded(HardCodedConfig), + HardCoded { hardcoded: HardCodedConfig }, } #[derive(Debug, Deserialize, Serialize, JsonSchema)] @@ -61,7 +61,12 @@ pub struct HardCodedConfig { impl AwsSigV4Config { pub fn is_disabled(&self) -> bool { - matches!(self.all, AwsSigV4SubgraphConfig::Disabled) && self.subgraphs.is_empty() + matches!(self.all, AwsSigV4SubgraphConfig::Disabled) + && (self.subgraphs.is_empty() + || self + .subgraphs + .values() + .all(|cfg| matches!(cfg, AwsSigV4SubgraphConfig::Disabled))) } }