From b9e90272ebcec4fbb9407ab3185d47fcf26a7b1e Mon Sep 17 00:00:00 2001 From: Jingwei Wang Date: Mon, 1 Jun 2026 18:54:21 +0000 Subject: [PATCH 1/8] sources: add bottlerocket-crypto-provider crate Add a centralized CryptoProvider crate that provides runtime FIPS detection and TLS algorithm selection for Bottlerocket Rust binaries. When the kernel FIPS flag is enabled (/proc/sys/crypto/fips_enabled = 1), the provider restricts TLS to FIPS-approved algorithms only (AES-GCM cipher suites, P-256/P-384 key exchange). On non-FIPS systems, the full algorithm set is available. Signed-off-by: Jingwei Wang --- sources/Cargo.lock | 1006 +++++++++-------- sources/Cargo.toml | 1 + .../bottlerocket-crypto-provider/Cargo.toml | 15 + .../bottlerocket-crypto-provider/src/lib.rs | 344 ++++++ 4 files changed, 905 insertions(+), 461 deletions(-) create mode 100644 sources/bottlerocket-crypto-provider/Cargo.toml create mode 100644 sources/bottlerocket-crypto-provider/src/lib.rs diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 7a4847168..b483b6571 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "actix-macros", "actix-rt", "actix_derive", - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "crossbeam-channel", "futures-core", @@ -81,7 +81,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "futures-core", "futures-sink", @@ -94,21 +94,21 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.12.1" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" +checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", "base64", - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "bytestring", "derive_more", "encoding_rs", - "foldhash 0.1.5", + "foldhash 0.2.0", "futures-core", "http 0.2.12", "httparse", @@ -119,7 +119,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.10.1", + "rand 0.10.2", "sha1 0.11.0", "smallvec", "tokio", @@ -134,7 +134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" dependencies = [ "actix-macros", "futures-core", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.13.0" +version = "4.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" dependencies = [ "actix-codec", "actix-http", @@ -217,7 +217,7 @@ dependencies = [ "cfg-if", "derive_more", "encoding_rs", - "foldhash 0.1.5", + "foldhash 0.2.0", "futures-core", "futures-util", "impl-more", @@ -232,7 +232,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.4", + "socket2 0.6.5", "time", "tracing", "url", @@ -264,7 +264,7 @@ checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "apiclient" @@ -413,10 +413,10 @@ dependencies = [ "models", "nix", "olpc-cjson", - "rand 0.8.6", + "rand 0.8.7", "reqwest", "retry-read", - "rustls 0.23.40", + "rustls 0.23.42", "serde", "serde_json", "signal-hook", @@ -426,7 +426,7 @@ dependencies = [ "test-case", "tokio", "tokio-tungstenite", - "toml", + "toml 0.8.23", "unindent", "url", ] @@ -454,7 +454,7 @@ dependencies = [ "models", "nix", "num", - "rand 0.8.6", + "rand 0.8.7", "serde", "serde_json", "serde_plain", @@ -463,14 +463,14 @@ dependencies = [ "snafu", "thar-be-updates", "tokio", - "toml", + "toml 0.8.23", ] [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -494,7 +494,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -508,9 +508,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "as_derive_utils" @@ -548,7 +548,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "synstructure", ] @@ -560,7 +560,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -593,7 +593,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -604,7 +604,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -621,9 +621,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.8.18" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33f815b73a3899c03b380d543532e5865f230dce9678d108dc10732a8682275" +checksum = "47712fde1909402600ccfbb26e47d482d2e58bb9e9e603d9f17e67cc435a6319" dependencies = [ "aws-credential-types", "aws-runtime", @@ -642,7 +642,7 @@ dependencies = [ "fastrand", "hex", "http 1.4.2", - "sha1 0.10.6", + "sha1 0.10.7", "time", "tokio", "tracing", @@ -652,9 +652,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.14" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" +checksum = "e93964ffdaf57857f544be3666a5f57570bb699e934700f11b49708f61bb556e" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -664,23 +664,24 @@ dependencies = [ [[package]] name = "aws-lc-fips-sys" -version = "0.13.14" +version = "0.13.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d619165468401dec3caa3366ebffbcb83f2f31883e5b3932f8e2dec2ddc568" +checksum = "6c0e6249c249b8916c98ebae7bc06216c8dcab3002f32872b4abe642d17063b1" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", + "pkg-config", "regex", ] [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", @@ -690,22 +691,23 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] name = "aws-runtime" -version = "1.7.5" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c9b9de216a988dd54b754a82a7660cfe14cee4f6782ae4524470972fa0ccb39" +checksum = "7816e98ee912159f45d307e5ee6bfea4a335a55aee15f7f3e32f81a6f3000f1d" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -722,7 +724,7 @@ dependencies = [ "http 0.2.12", "http 1.4.2", "http-body 0.4.6", - "http-body 1.0.1", + "http-body 1.1.0", "percent-encoding", "pin-project-lite", "tracing", @@ -731,9 +733,9 @@ dependencies = [ [[package]] name = "aws-sdk-cloudformation" -version = "1.116.0" +version = "1.118.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6645c79455599dc449f06e950bd540d799737a871ae8b402fa5664b2d7d0f7c8" +checksum = "851b6a08d1d65691810acf810f1eeb86a14d2177e87738b059ed5b3c994adf32" dependencies = [ "arc-swap", "aws-credential-types", @@ -745,6 +747,7 @@ dependencies = [ "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-smithy-xml", "aws-types", @@ -757,9 +760,9 @@ dependencies = [ [[package]] name = "aws-sdk-ec2" -version = "1.232.0" +version = "1.240.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed1c72b10833ce8a789a21c1b95df11387932d7e9ee9a2931635bdb6770a0ad" +checksum = "b6c0843c7c1d89153b30041cc890b8bb24ccd9e845bb2b37f78335e8199da006" dependencies = [ "arc-swap", "aws-credential-types", @@ -771,6 +774,7 @@ dependencies = [ "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-smithy-xml", "aws-types", @@ -783,9 +787,9 @@ dependencies = [ [[package]] name = "aws-sdk-eks" -version = "1.135.0" +version = "1.138.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce304515465d273f7a1e5addf6431246d2efd2ef4b45c1378cc2ce7c55ac0fd4" +checksum = "82bdc8aee0ce30ee626466ce89473041700cbdedc4b663d5dd93d49aab0c70c3" dependencies = [ "arc-swap", "aws-credential-types", @@ -796,6 +800,7 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", @@ -808,9 +813,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.137.0" +version = "1.138.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd7213994e2ff9382ff100403b78c30d1b74cdfcd8fa9d0d1dc3a94a5c4874" +checksum = "7c29be98554a0deea25d4eaca131240a224dcbcaf20357e35cc432e17b721ab5" dependencies = [ "arc-swap", "aws-credential-types", @@ -824,6 +829,7 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-smithy-xml", "aws-types", @@ -833,7 +839,7 @@ dependencies = [ "hmac 0.13.0", "http 0.2.12", "http 1.4.2", - "http-body 1.0.1", + "http-body 1.1.0", "lru", "percent-encoding", "regex-lite", @@ -844,9 +850,9 @@ dependencies = [ [[package]] name = "aws-sdk-secretsmanager" -version = "1.108.0" +version = "1.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd24cfd47bda71881c399a7cc4850d5a61727246163d5cd1a7c0b48ef19983cb" +checksum = "661ce910a6ed895c6bde66aa78040272628b127714fe0e9bb799f07ab4db407b" dependencies = [ "arc-swap", "aws-credential-types", @@ -857,6 +863,7 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", @@ -869,9 +876,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssm" -version = "1.113.0" +version = "1.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fbb033ddd700b6475dd8da44623fab28f5e6d8d1b91d839a7b5e09ed0e5c1e" +checksum = "31275f2d68a77c41e7042103b987410b870522fc54644677f220caf0b7b329a9" dependencies = [ "arc-swap", "aws-credential-types", @@ -882,6 +889,7 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", @@ -894,9 +902,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.102.0" +version = "1.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c82b3ac19f1431854f7ace3a7531674633e286bfdde21976893bfee36fd493b" +checksum = "0469f435f645ad2162cfb463b15bde37115966ee3acf2d87fb4871ee309b8401" dependencies = [ "arc-swap", "aws-credential-types", @@ -907,6 +915,7 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", @@ -919,9 +928,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.104.0" +version = "1.105.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321000d2b4c5519ee573f73167f612efd7329322d9b26969ad1979f0427f1913" +checksum = "085faefb253f770655e162b9304321e62a1e71adf7f019ee1f4454228a377b3a" dependencies = [ "arc-swap", "aws-credential-types", @@ -932,6 +941,7 @@ dependencies = [ "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", @@ -944,9 +954,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.107.0" +version = "1.108.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0d328ba962af23ecfa3c9f23b98d3d35e325fa218d7f13d17a6bf522f8a560" +checksum = "3c72b08911d8128dd360fe1b22a9fec0fa8b552dde8ec828dcf20ef5ec974e9f" dependencies = [ "arc-swap", "aws-credential-types", @@ -958,6 +968,7 @@ dependencies = [ "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-smithy-xml", "aws-types", @@ -970,9 +981,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.4.5" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71" +checksum = "723c2234ad7511ceef63eab016b7ba6ff7c55590fefb96fa8467af014a07309f" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -997,9 +1008,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.14" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" +checksum = "f02e407fb3b54891734224b9ffac8a71fdd35f542500fa1af95754a6b2beb316" dependencies = [ "futures-util", "pin-project-lite", @@ -1008,9 +1019,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.64.8" +version = "0.65.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8e65f4f81fcccdeb6c3eca2af17ac21d421a1786a26a394aecf421d616d3a" +checksum = "b67ecd999972b58e67cab052f5129906c08c25883bd0788ceefc55ef97d61307" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -1018,7 +1029,7 @@ dependencies = [ "crc-fast", "hex", "http 1.4.2", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "md-5", "pin-project-lite", @@ -1029,9 +1040,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.21" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78d8391e65fcea47c586a22e1a41f173b38615b112b2c6b7a44e80cec3e6b706" +checksum = "5a9381123ab62d20c13082b151f30f962a3b112b727345394536dfa39a482944" dependencies = [ "aws-smithy-types", "bytes", @@ -1055,7 +1066,7 @@ dependencies = [ "hyper-util", "once_cell", "pin-project-lite", - "rustls 0.23.40", + "rustls 0.23.42", "tokio", "tower 0.4.13", "tracing", @@ -1064,9 +1075,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.63.6" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" +checksum = "37843d9add67c3aff5856f409c6dc315d3cdff60f9c0cb5b670dab1e9920306d" dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", @@ -1076,7 +1087,7 @@ dependencies = [ "futures-core", "futures-util", "http 1.4.2", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "percent-encoding", "pin-project-lite", @@ -1086,9 +1097,9 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.1.13" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3ef8931ad1c98aa6a55b4256f847f3116090819844e0dd41ea682cac5dd2d3" +checksum = "635d23afda0a6ab48d666c4d447c4873e8d1e83518a2be2093122397e50b838e" dependencies = [ "aws-smithy-async", "aws-smithy-protocol-test", @@ -1100,7 +1111,7 @@ dependencies = [ "http 0.2.12", "http 1.4.2", "http-body 0.4.6", - "http-body 1.0.1", + "http-body 1.1.0", "hyper 0.14.32", "hyper 1.10.1", "hyper-rustls 0.24.2", @@ -1109,7 +1120,7 @@ dependencies = [ "indexmap", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.40", + "rustls 0.23.42", "rustls-native-certs 0.8.4", "rustls-pki-types", "serde", @@ -1122,9 +1133,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.7" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa" +checksum = "3dc65a121adb4b33729919fcfa14fa36fb33c1555a8f06bb0e2188dbfdc1d9ef" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -1133,18 +1144,18 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" +checksum = "8e86338c869539a581bf161247762a6e87f92c5c075060057b5ed6d06632ed0c" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-protocol-test" -version = "0.63.14" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b227aa94af99a8e5ee52551cc7e3ee30a217019ef99207b6f0b7a1527685941" +checksum = "f76511a0e223ce78deb6a78b8afebda99cb737cfbc8a58d96dcb190f012dd40a" dependencies = [ "assert-json-diff", "aws-smithy-runtime-api", @@ -1161,9 +1172,9 @@ dependencies = [ [[package]] name = "aws-smithy-query" -version = "0.60.15" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" +checksum = "dd22a6ba36e3f113cb8d5b3d1fe0ed31c76ee608ef63322d753bb8d2c9479e77" dependencies = [ "aws-smithy-types", "urlencoding", @@ -1171,9 +1182,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" +checksum = "bea94a9ff8464016338c851e24b472d7131c388c88898a502e781815b2ee6045" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1187,7 +1198,7 @@ dependencies = [ "http 0.2.12", "http 1.4.2", "http-body 0.4.6", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "pin-project-lite", "pin-utils", @@ -1198,9 +1209,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c" +checksum = "22ed1ebe6e0a95ea84570225f5a8208dec4b8f77e61a9b0d6f51773fcb4612f0" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -1216,20 +1227,20 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api-macros" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +checksum = "221eaa237ddf1ca79b60d1372aad77e47f9c0ea5b3ce5099da8c61d027dc77b3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "aws-smithy-schema" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -1238,9 +1249,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b42fcf341259d85ca10fac9a2f6448a8ec691c6955a18e45bc3b71a85fab85" +checksum = "d6dc683efb34b9e755675b37fedbe0103141e5b6df7bdc9eb6967756a8c167d8" dependencies = [ "base64-simd", "bytes", @@ -1249,7 +1260,7 @@ dependencies = [ "http 0.2.12", "http 1.4.2", "http-body 0.4.6", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "itoa", "num-integer", @@ -1264,18 +1275,21 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.15" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +checksum = "ea3f68eec3607f02acd24067969ce2abc6ba16aa7d5ce59ca450ed2fb5f78957" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.16" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" +checksum = "e957a6c6dbce82b7a91f44231c09273159703769f447cbe85e854dfe9cf67f86" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -1344,7 +1358,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "cexpr", "clang-sys", "itertools", @@ -1354,8 +1368,8 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", - "syn 2.0.118", + "shlex 1.3.0", + "syn 2.0.119", ] [[package]] @@ -1372,15 +1386,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -1448,7 +1462,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1467,7 +1481,7 @@ dependencies = [ "simplelog", "snafu", "tempfile", - "toml", + "toml 0.8.23", ] [[package]] @@ -1483,22 +1497,22 @@ dependencies = [ "serde_json", "simplelog", "snafu", - "toml", + "toml 0.8.23", ] [[package]] name = "bork" version = "0.1.0" dependencies = [ - "rand 0.8.6", + "rand 0.8.7", "serde_json", ] [[package]] name = "borsh" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" dependencies = [ "borsh-derive", "bytes", @@ -1507,15 +1521,26 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", +] + +[[package]] +name = "bottlerocket-crypto-provider" +version = "0.1.0" +dependencies = [ + "aws-lc-rs", + "log", + "rcgen", + "rustls 0.23.42", + "snafu", ] [[package]] @@ -1535,7 +1560,7 @@ source = "git+https://github.com/bottlerocket-os/bottlerocket-settings-sdk?tag=b dependencies = [ "darling", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1594,7 +1619,7 @@ dependencies = [ "quote", "serde", "serde_plain", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1605,7 +1630,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1646,7 +1671,7 @@ dependencies = [ "settings-extension-oci-hooks", "settings-extension-pki", "settings-extension-updates", - "toml", + "toml 0.8.23", ] [[package]] @@ -1691,7 +1716,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1711,10 +1736,10 @@ dependencies = [ "itertools", "pathdiff", "serde", - "shlex", + "shlex 1.3.0", "snafu", "tempfile", - "toml", + "toml 0.8.23", "which", ] @@ -1740,12 +1765,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" dependencies = [ "memchr", - "serde", + "serde_core", ] [[package]] @@ -1756,9 +1781,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byte-unit" -version = "5.2.3" +version = "5.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37bcaa4a0975bed4a760af3efe4368825098ce5f9d37a30c5a021d635dc63d8f" +checksum = "4a813de7f2bbedb7dce265b64f1cf5908ebe4d56281ece8d847e98113788b9b0" dependencies = [ "rust_decimal", "schemars", @@ -1796,9 +1821,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bytes-utils" @@ -1846,7 +1871,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1857,16 +1882,15 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cargo-readme" -version = "3.3.1" +version = "3.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59307ab9e1b42ea85dbfa9f062ebcbec02b723a76034094538cd65bfbf15c725" +checksum = "1eb6777157f3f996589c9f4d008e14a4b4af43d793eae635f287439ebd471819" dependencies = [ "clap", - "lazy_static", "percent-encoding", "regex", "serde", - "toml", + "toml 1.1.3+spec-1.1.0", ] [[package]] @@ -1890,14 +1914,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.58" +version = "1.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -1914,7 +1938,7 @@ dependencies = [ "simplelog", "snafu", "tempfile", - "toml", + "toml 0.8.23", "x509-parser", ] @@ -1951,19 +1975,19 @@ dependencies = [ "generate-readme", "imdsclient", "log", - "rustls 0.23.40", + "rustls 0.23.42", "serde", "simplelog", "snafu", "tokio", - "toml", + "toml 0.8.23", ] [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -2028,9 +2052,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" dependencies = [ "clap_builder", "clap_derive", @@ -2038,9 +2062,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ "anstream", "anstyle", @@ -2057,7 +2081,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2189,7 +2213,7 @@ dependencies = [ "snafu", "tempfile", "test-case", - "toml", + "toml 0.8.23", ] [[package]] @@ -2246,18 +2270,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crunchy" @@ -2326,7 +2350,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2337,7 +2361,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2358,10 +2382,41 @@ dependencies = [ "serde_json", "serde_plain", "snafu", - "toml", + "toml 0.8.23", "walkdir", ] +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "der" version = "0.7.10" @@ -2401,7 +2456,7 @@ checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2423,7 +2478,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.118", + "syn 2.0.119", "unicode-xid", ] @@ -2465,7 +2520,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2476,7 +2531,7 @@ checksum = "cf5597a4b7fe5275fc9dcf88ce26326bc8e4cb87d0130f33752d4c5f717793cf" dependencies = [ "cfg-if", "libc", - "socket2 0.6.4", + "socket2 0.6.5", "windows-sys 0.60.2", ] @@ -2506,7 +2561,7 @@ dependencies = [ "simplelog", "snafu", "tempfile", - "toml", + "toml 0.8.23", "walkdir", ] @@ -2541,7 +2596,7 @@ dependencies = [ "simplelog", "snafu", "tokio", - "toml", + "toml 0.8.23", "walkdir", ] @@ -2560,7 +2615,7 @@ dependencies = [ "serde", "serde_json", "snafu", - "toml", + "toml 0.8.23", ] [[package]] @@ -2663,14 +2718,14 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -2678,9 +2733,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -2895,7 +2950,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -2968,20 +3023,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -2989,9 +3030,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi 6.0.0", + "r-efi", "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -3025,14 +3068,14 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd" dependencies = [ "aho-corasick", - "bstr 1.12.1", + "bstr 1.13.0", "log", - "regex-automata 0.4.14", + "regex-automata 0.4.16", "regex-syntax", ] @@ -3171,7 +3214,7 @@ dependencies = [ "http 1.4.2", "httpdate", "mime", - "sha1 0.10.6", + "sha1 0.10.7", ] [[package]] @@ -3257,7 +3300,7 @@ dependencies = [ "simplelog", "snafu", "tempfile", - "toml", + "toml 0.8.23", ] [[package]] @@ -3294,9 +3337,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http 1.4.2", @@ -3304,14 +3347,14 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", "http 1.4.2", - "http-body 1.0.1", + "http-body 1.1.0", "pin-project-lite", ] @@ -3351,9 +3394,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] @@ -3394,7 +3437,7 @@ dependencies = [ "futures-core", "h2 0.4.15", "http 1.4.2", - "http-body 1.0.1", + "http-body 1.1.0", "httparse", "itoa", "pin-project-lite", @@ -3447,7 +3490,7 @@ dependencies = [ "http 1.4.2", "hyper 1.10.1", "hyper-util", - "rustls 0.23.40", + "rustls 0.23.42", "rustls-native-certs 0.8.4", "tokio", "tokio-rustls 0.26.4", @@ -3478,13 +3521,13 @@ dependencies = [ "futures-channel", "futures-util", "http 1.4.2", - "http-body 1.0.1", + "http-body 1.1.0", "hyper 1.10.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.5", "tokio", "tower-service", "tracing", @@ -3637,7 +3680,7 @@ dependencies = [ "snafu", "tempfile", "test-case", - "toml", + "toml 0.8.23", "typed-path", "walkdir", ] @@ -3660,9 +3703,9 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.1.9" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" +checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" [[package]] name = "indexmap" @@ -3708,10 +3751,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "961d16382652bfdd8c6f68b223b26a8c93e0d475c672f414411db31c6c5c900e" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -3721,30 +3765,30 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.102" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", @@ -3859,9 +3903,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "logdog" @@ -3872,7 +3916,7 @@ dependencies = [ "generate-readme", "glob", "reqwest", - "rustls 0.23.40", + "rustls 0.23.42", "serde_json", "shell-words", "snafu", @@ -3929,7 +3973,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.4.14", + "regex-automata 0.4.16", ] [[package]] @@ -3944,9 +3988,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memoffset" @@ -3977,13 +4021,13 @@ dependencies = [ "httptest", "log", "reqwest", - "rustls 0.23.40", + "rustls 0.23.42", "serde", "serde_json", "simplelog", "snafu", "tempfile", - "toml", + "toml 0.8.23", "url", ] @@ -4006,7 +4050,7 @@ dependencies = [ "nix", "pentacle", "percent-encoding", - "rand 0.8.6", + "rand 0.8.7", "semver", "serde_json", "simplelog", @@ -4045,9 +4089,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "log", @@ -4068,7 +4112,7 @@ dependencies = [ "serde_json", "serde_plain", "tempfile", - "toml", + "toml 0.8.23", ] [[package]] @@ -4083,7 +4127,7 @@ dependencies = [ "indexmap", "ipnet", "lazy_static", - "rand 0.8.6", + "rand 0.8.7", "regex", "serde", "serde_json", @@ -4092,7 +4136,7 @@ dependencies = [ "systemd-derive", "tempfile", "tokio", - "toml", + "toml 0.8.23", ] [[package]] @@ -4143,9 +4187,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -4174,7 +4218,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4188,11 +4232,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -4419,9 +4462,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" dependencies = [ "memchr", "ucd-trie", @@ -4429,9 +4472,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" dependencies = [ "pest", "pest_generator", @@ -4439,25 +4482,24 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "pest_meta" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" dependencies = [ "pest", - "sha2 0.10.9", ] [[package]] @@ -4477,7 +4519,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4502,6 +4544,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "pluto" version = "0.1.0" @@ -4523,7 +4571,7 @@ dependencies = [ "httptest", "imdsclient", "log", - "rustls 0.23.40", + "rustls 0.23.42", "serde", "serde_json", "simplelog", @@ -4591,7 +4639,7 @@ dependencies = [ "signpost", "simplelog", "snafu", - "toml", + "toml 0.8.23", ] [[package]] @@ -4611,7 +4659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4625,11 +4673,11 @@ 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 0.23.10+spec-1.0.0", + "toml_edit 0.25.13+spec-1.1.0", ] [[package]] @@ -4663,9 +4711,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -4673,8 +4721,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.40", - "socket2 0.5.10", + "rustls 0.23.42", + "socket2 0.6.5", "thiserror 2.0.18", "tokio", "tracing", @@ -4683,17 +4731,18 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "bytes", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.4", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", - "rustls 0.23.40", + "rustls 0.23.42", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -4704,33 +4753,27 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.5", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -4745,30 +4788,20 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", "getrandom 0.4.3", @@ -4785,16 +4818,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -4806,18 +4829,31 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "getrandom 0.3.4", + "rand_core 0.10.1", ] [[package]] -name = "rand_core" -version = "0.10.1" +name = "rcgen" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] [[package]] name = "redox_syscall" @@ -4825,7 +4861,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", ] [[package]] @@ -4845,18 +4881,18 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.14", + "regex-automata 0.4.16", "regex-syntax", ] @@ -4868,9 +4904,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -4919,7 +4955,7 @@ dependencies = [ "futures-core", "futures-util", "http 1.4.2", - "http-body 1.0.1", + "http-body 1.1.0", "http-body-util", "hyper 1.10.1", "hyper-rustls 0.27.9", @@ -4929,7 +4965,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.40", + "rustls 0.23.42", "rustls-native-certs 0.8.4", "rustls-pki-types", "serde", @@ -5046,7 +5082,7 @@ dependencies = [ "borsh", "bytes", "num-traits", - "rand 0.8.6", + "rand 0.8.7", "rkyv", "serde", "serde_json", @@ -5055,15 +5091,15 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -5089,11 +5125,11 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5102,7 +5138,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -5123,9 +5159,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", @@ -5173,9 +5209,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -5205,9 +5241,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -5282,7 +5318,7 @@ dependencies = [ "tempfile", "test-case", "tokio", - "toml", + "toml 0.8.23", "url", ] @@ -5328,7 +5364,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -5341,7 +5377,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -5413,7 +5449,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -5447,7 +5483,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -5459,6 +5495,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5604,7 +5649,7 @@ dependencies = [ "regex", "serde", "serde_json", - "toml", + "toml 0.8.23", ] [[package]] @@ -5696,7 +5741,7 @@ dependencies = [ "env_logger", "serde", "serde_json", - "toml", + "toml 0.8.23", ] [[package]] @@ -5789,7 +5834,7 @@ dependencies = [ "env_logger", "serde", "serde_json", - "toml", + "toml 0.8.23", ] [[package]] @@ -5827,16 +5872,16 @@ dependencies = [ "bottlerocket-modeled-types", "bottlerocket-settings-sdk", "env_logger", - "rand 0.8.6", + "rand 0.8.7", "serde", "serde_json", ] [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -5906,7 +5951,7 @@ dependencies = [ "snafu", "tempfile", "tokio", - "toml", + "toml 0.8.23", ] [[package]] @@ -5915,6 +5960,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook" version = "0.3.18" @@ -5960,9 +6011,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simdutf8" @@ -6026,7 +6077,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6041,9 +6092,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -6051,9 +6102,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" [[package]] name = "spki" @@ -6083,7 +6134,7 @@ dependencies = [ "simplelog", "snafu", "tempfile", - "toml", + "toml 0.8.23", ] [[package]] @@ -6097,12 +6148,12 @@ dependencies = [ "generate-readme", "log", "models", - "rand 0.8.6", + "rand 0.8.7", "semver", "serde_json", "simplelog", "snafu", - "toml", + "toml 0.8.23", ] [[package]] @@ -6130,7 +6181,7 @@ dependencies = [ "models", "serde", "serde_json", - "shlex", + "shlex 1.3.0", "simplelog", "snafu", "tokio", @@ -6149,9 +6200,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -6175,7 +6226,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6186,7 +6237,7 @@ dependencies = [ "generate-readme", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6212,7 +6263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -6245,7 +6296,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6256,7 +6307,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "test-case-core", ] @@ -6272,7 +6323,7 @@ dependencies = [ "snafu", "tempfile", "test-case", - "toml", + "toml 0.8.23", "url", "zeroize", ] @@ -6320,7 +6371,7 @@ dependencies = [ "simplelog", "snafu", "tempfile", - "toml", + "toml 0.8.23", "update_metadata", ] @@ -6350,7 +6401,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6361,23 +6412,23 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] [[package]] name = "time" -version = "0.3.49" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", "libc", @@ -6397,9 +6448,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -6417,9 +6468,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -6442,7 +6493,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.4", + "socket2 0.6.5", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -6456,7 +6507,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6466,7 +6517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a129d95275ebf4c493ec53bf0f8cd95f5ac161bc4f381700809a54f595d4470" dependencies = [ "pin-project-lite", - "rand 0.10.1", + "rand 0.10.2", "tokio", ] @@ -6486,7 +6537,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.40", + "rustls 0.23.42", "tokio", ] @@ -6546,11 +6597,26 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "indexmap", "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -6562,9 +6628,9 @@ 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", ] @@ -6577,31 +6643,31 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.4", ] [[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", + "winnow 1.0.4", ] [[package]] @@ -6610,6 +6676,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tough" version = "0.22.0" @@ -6631,7 +6703,7 @@ dependencies = [ "pem", "percent-encoding", "reqwest", - "rustls 0.23.40", + "rustls 0.23.42", "serde", "serde_json", "serde_plain", @@ -6677,11 +6749,11 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "futures-util", "http 1.4.2", - "http-body 1.0.1", + "http-body 1.1.0", "pin-project-lite", "tower 0.5.3", "tower-layer", @@ -6721,7 +6793,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6764,7 +6836,7 @@ dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex-automata 0.4.14", + "regex-automata 0.4.16", "serde", "serde_json", "sharded-slab", @@ -6809,8 +6881,8 @@ dependencies = [ "http 0.2.12", "httparse", "log", - "rand 0.8.6", - "sha1 0.10.6", + "rand 0.8.7", + "sha1 0.10.7", "thiserror 1.0.69", "url", "utf-8", @@ -6920,7 +6992,7 @@ dependencies = [ "serde_json", "serde_plain", "snafu", - "toml", + "toml 0.8.23", ] [[package]] @@ -6949,7 +7021,7 @@ dependencies = [ "tempfile", "tokio", "tokio-util", - "toml", + "toml 0.8.23", "tough", "update_metadata", "url", @@ -6981,9 +7053,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" +checksum = "159a7cadce548703edd50d24069bc294c5415ecab0a480e0cd1ca06d112dc94a" [[package]] name = "utf8_iter" @@ -6999,9 +7071,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.3" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -7093,20 +7165,11 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -7117,9 +7180,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.75" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -7127,9 +7190,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7137,22 +7200,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] @@ -7172,9 +7235,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.102" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -7217,7 +7280,7 @@ dependencies = [ "snafu", "test-case", "tokio", - "toml", + "toml 0.8.23", "zbus", "zvariant", ] @@ -7274,7 +7337,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -7285,7 +7348,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -7321,6 +7384,15 @@ 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" @@ -7478,10 +7550,13 @@ dependencies = [ ] [[package]] -name = "wit-bindgen" -version = "0.57.1" +name = "winnow" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] [[package]] name = "writeable" @@ -7542,6 +7617,15 @@ 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.3" @@ -7561,15 +7645,15 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "synstructure", ] [[package]] name = "zbus" -version = "5.14.0" +version = "5.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +checksum = "a28b97f866896a4be7aefd2b5a8e01bb6773d19a775d54ab28b4d094b9a4480e" dependencies = [ "async-broadcast", "async-recursion", @@ -7589,7 +7673,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow", + "winnow 1.0.4", "zbus_macros", "zbus_names", "zvariant", @@ -7597,14 +7681,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.14.0" +version = "5.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +checksum = "5e05ad887425eecf5e8384dc2406a4a9313eb73468712fc1cdea362eb4fe0469" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "zbus_names", "zvariant", "zvariant_utils", @@ -7612,33 +7696,33 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.1" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +checksum = "1039ca249fee9559680f3a9f05b55e0761fee51af4f6c1e7d8c1f31e549721d2" dependencies = [ "serde", - "winnow", + "winnow 1.0.4", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.52" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.52" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -7658,7 +7742,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "synstructure", ] @@ -7679,7 +7763,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -7712,51 +7796,51 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zvariant" -version = "5.10.0" +version = "5.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +checksum = "7cf057bb00bf5c9ad77abb6147b0ca4818236a1858416e9d988e40d6322fefa7" dependencies = [ "endi", "enumflags2", "serde", - "winnow", + "winnow 1.0.4", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.10.0" +version = "5.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +checksum = "8118ca6bda77bfc0ab51d660db0c955f2505eef854c9a449435bccb616933b31" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.118", - "winnow", + "syn 2.0.119", + "winnow 1.0.4", ] diff --git a/sources/Cargo.toml b/sources/Cargo.toml index b9d7f8a87..9d120240e 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -85,6 +85,7 @@ members = [ "xfscli", "whippet", + "bottlerocket-crypto-provider", ] [workspace.dependencies] diff --git a/sources/bottlerocket-crypto-provider/Cargo.toml b/sources/bottlerocket-crypto-provider/Cargo.toml new file mode 100644 index 000000000..c79c7a103 --- /dev/null +++ b/sources/bottlerocket-crypto-provider/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bottlerocket-crypto-provider" +version = "0.1.0" +license = "Apache-2.0 OR MIT" +edition = "2021" +publish = false + +[dependencies] +aws-lc-rs = { workspace = true, features = ["fips"] } +log = { workspace = true } +rustls = { workspace = true } +snafu.workspace = true + +[dev-dependencies] +rcgen = "0.13" diff --git a/sources/bottlerocket-crypto-provider/src/lib.rs b/sources/bottlerocket-crypto-provider/src/lib.rs new file mode 100644 index 000000000..34f31d9a8 --- /dev/null +++ b/sources/bottlerocket-crypto-provider/src/lib.rs @@ -0,0 +1,344 @@ +//! Centralized CryptoProvider for Bottlerocket Rust binaries. +//! +//! Provides runtime FIPS detection and TLS algorithm selection. +//! When the kernel FIPS flag is enabled (`/proc/sys/crypto/fips_enabled` = 1), +//! the provider restricts TLS to FIPS-approved algorithms only. +//! +//! The FIPS filtering uses rustls's `.fips()` trait method on each cipher suite +//! and key exchange group, which queries the underlying aws-lc-rs library to +//! determine FIPS approval. This means we automatically stay in sync with +//! upstream's FIPS classifications without maintaining a manual allowlist. +//! +//! # Usage +//! +//! Call [`install_provider`] at the very start of `main()`, before any TLS +//! connections are made. This installs the appropriate `CryptoProvider` as the +//! process-wide global default for all rustls usage, including downstream +//! libraries like `reqwest`, `hyper-rustls`, and `aws-smithy-http-client`. +//! +//! ```rust,ignore +//! fn main() { +//! bottlerocket_crypto_provider::install_provider() +//! .expect("failed to install crypto provider"); +//! +//! // All subsequent TLS connections automatically use the installed +//! // provider — no per-client configuration needed. +//! } +//! ``` +//! +//! If you need a [`CryptoProvider`] instance directly (e.g. to pass to +//! `ClientConfig::builder_with_provider()`), use [`provider`]: +//! +//! ```rust,ignore +//! use std::sync::Arc; +//! +//! let crypto = bottlerocket_crypto_provider::provider() +//! .expect("failed to detect FIPS mode"); +//! let config = rustls::ClientConfig::builder_with_provider(Arc::new(crypto)) +//! .with_safe_default_protocol_versions() +//! .unwrap() +//! .with_root_certificates(root_store) +//! .with_no_client_auth(); +//! ``` +//! +//! # How it works +//! +//! 1. Reads `/proc/sys/crypto/fips_enabled` to detect if the kernel has FIPS +//! mode enabled. +//! 2. If FIPS is enabled, returns a provider filtered to only FIPS-approved +//! algorithms (AES-GCM cipher suites, P-256/P-384/X25519MLKEM768 key +//! exchange groups). +//! 3. If FIPS is not enabled, returns the full default provider with all +//! algorithms available (including ChaCha20 and X25519). +//! +//! # Important +//! +//! - [`install_provider`] MUST be called before any other code installs a +//! `CryptoProvider`. The global default uses `OnceLock` and cannot be +//! overridden once set. If another provider is already installed, it returns +//! [`Error::ProviderAlreadyInstalled`]. +//! - The `aws-lc-rs` dependency MUST be compiled with `features = ["fips"]` so +//! that the underlying C library reports `FIPS_mode() == 1`. Without this, +//! `.fips()` returns `false` for all algorithms, and `fips_provider()` would +//! filter out everything — resulting in an empty provider with no cipher +//! suites or kx groups, and all TLS connections would fail. + +use log::info; +use rustls::crypto::{aws_lc_rs, CryptoProvider}; +use snafu::{ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("Failed to read /proc/sys/crypto/fips_enabled"))] + ReadFipsEnabled { source: std::io::Error }, + + #[snafu(display( + "CryptoProvider already installed before install_provider() was called. \ + install_provider() must be the first thing called in main()." + ))] + ProviderAlreadyInstalled, +} + +/// Detect whether the system is running in FIPS mode by reading the kernel flag. +pub fn fips_enabled() -> Result { + match std::fs::read_to_string("/proc/sys/crypto/fips_enabled") { + Ok(content) => Ok(content.trim() == "1"), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false), + Err(e) => Err(e).context(ReadFipsEnabledSnafu), + } +} + +/// Returns a `CryptoProvider` restricted to FIPS-approved algorithms. +/// +/// Filters the default aws-lc-rs provider to only include cipher suites and +/// key exchange groups that report `.fips() == true`. This delegates the +/// FIPS classification to upstream rustls/aws-lc-rs rather than maintaining +/// a manual allowlist. +/// +/// Each algorithm's `.fips()` returns true only when the underlying aws-lc-rs +/// library is in FIPS mode (`FIPS_mode() == 1`, compile-time constant) AND the +/// specific algorithm is FIPS-approved. +fn fips_provider() -> CryptoProvider { + let base = aws_lc_rs::default_provider(); + CryptoProvider { + cipher_suites: base + .cipher_suites + .into_iter() + // Upstream rustls `.fips()` on cipher suites (rustls v/0.23.40): + // https://github.com/rustls/rustls/blob/v/0.23.40/rustls/src/crypto/aws_lc_rs/tls13.rs#L116 + // https://github.com/rustls/rustls/blob/v/0.23.40/rustls/src/crypto/aws_lc_rs/tls12.rs#L190 + // After filtering, the following FIPS-approved suites remain: + // - TLS13_AES_256_GCM_SHA384 + // - TLS13_AES_128_GCM_SHA256 + // - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + // - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + // - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + // - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + .filter(|s| s.fips()) + .collect(), + kx_groups: base + .kx_groups + .into_iter() + // Upstream rustls `.fips()` on kx groups (rustls v/0.23.40): + // https://github.com/rustls/rustls/blob/v/0.23.40/rustls/src/crypto/ring/kx.rs#L69 + // https://github.com/rustls/rustls/blob/v/0.23.40/rustls/src/crypto/aws_lc_rs/pq/hybrid.rs#L71 + // After filtering, the following FIPS-approved groups remain: + // - secp256r1 + // - secp384r1 + // - X25519MLKEM768 + .filter(|g| g.fips()) + .collect(), + ..base + } +} + +/// Returns the default `CryptoProvider` with all algorithms available. +fn default_provider() -> CryptoProvider { + aws_lc_rs::default_provider() +} + +/// Returns the appropriate `CryptoProvider` based on runtime FIPS detection. +pub fn provider() -> Result { + let (crypto_mode, provider) = if fips_enabled()? { + ("FIPS", fips_provider()) + } else { + ("default", default_provider()) + }; + info!("Using {} CryptoProvider", crypto_mode); + Ok(provider) +} + +/// Detect FIPS mode and install the appropriate `CryptoProvider` as the global default. +/// +/// This MUST be called once at the very start of `main()`, before any TLS +/// connections are made or any library has a chance to install its own provider. +/// The global provider uses `OnceLock` internally and cannot be overridden once set. +pub fn install_provider() -> Result<(), Error> { + provider()? + .install_default() + .map_err(|_| Error::ProviderAlreadyInstalled)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use rustls::crypto::aws_lc_rs; + use rustls::{CipherSuite, NamedGroup}; + use std::sync::Arc; + + #[test] + fn fips_provider_excludes_chacha20() { + // Given a FIPS-restricted provider + let p = fips_provider(); + + // Then no ChaCha20 cipher suites are present + assert!(p.cipher_suites.iter().all(|s| { + s.suite() != CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 + && s.suite() != CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + && s.suite() != CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + })); + } + + #[test] + fn fips_provider_excludes_x25519() { + // Given a FIPS-restricted provider + let p = fips_provider(); + + // Then X25519 is not in the kx groups + assert!(p.kx_groups.iter().all(|g| g.name() != NamedGroup::X25519)); + } + + #[test] + fn fips_provider_has_aes_gcm() { + // Given a FIPS-restricted provider + let p = fips_provider(); + + // Then AES-GCM cipher suites are present + assert!(!p.cipher_suites.is_empty()); + assert!(p + .cipher_suites + .iter() + .any(|s| s.suite() == CipherSuite::TLS13_AES_256_GCM_SHA384)); + } + + #[test] + fn fips_provider_has_p256_p384() { + // Given a FIPS-restricted provider + let p = fips_provider(); + + // Then P-256 and P-384 kx groups are present + assert!(!p.kx_groups.is_empty()); + assert!(p + .kx_groups + .iter() + .any(|g| g.name() == NamedGroup::secp256r1)); + assert!(p + .kx_groups + .iter() + .any(|g| g.name() == NamedGroup::secp384r1)); + } + + #[test] + fn default_provider_includes_chacha20() { + // Given the default (non-FIPS) provider + let p = default_provider(); + + // Then ChaCha20 cipher suites are available + assert!(p + .cipher_suites + .iter() + .any(|s| s.suite() == CipherSuite::TLS13_CHACHA20_POLY1305_SHA256)); + } + + #[test] + fn default_provider_includes_x25519() { + // Given the default (non-FIPS) provider + let p = default_provider(); + + // Then X25519 kx group is available + assert!(p.kx_groups.iter().any(|g| g.name() == NamedGroup::X25519)); + } + + #[test] + fn fips_enabled_reads_system_state() { + // Given the system's FIPS state + // When fips_enabled() is called + match fips_enabled() { + Ok(enabled) => { + // Then the result matches the kernel flag + let raw = + std::fs::read_to_string("/proc/sys/crypto/fips_enabled").unwrap_or_default(); + assert_eq!(enabled, raw.trim() == "1"); + } + Err(_) => { + // Then on systems without the file, an error is acceptable + } + } + } + + /// Validates that a FIPS-restricted client cannot complete a TLS handshake + /// with a server offering only non-FIPS cipher suites (ChaCha20-Poly1305). + /// + /// This test uses an in-memory byte buffer instead of real TCP sockets. + /// TLS cipher negotiation is a protocol-level concern. It only depends on + /// the ClientHello and ServerHello message contents, not on how bytes are + /// transported. The rustls `ClientConnection` and `ServerConnection` state + /// machines operate on raw bytes via `write_tls`/`read_tls`, so we can feed + /// them through a `Vec` and get the exact same negotiation result as a + /// real TCP connection, without needing threads, sockets, or network access. + #[test] + fn fips_provider_rejects_non_fips_server() { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); + let server_cert = rustls::pki_types::CertificateDer::from(cert.cert); + let server_key = + rustls::pki_types::PrivateKeyDer::try_from(cert.key_pair.serialize_der()).unwrap(); + + // Given a server configured with only ChaCha20-Poly1305 (non-FIPS) ciphers + let server_provider = { + let base = aws_lc_rs::default_provider(); + CryptoProvider { + cipher_suites: base + .cipher_suites + .into_iter() + .filter(|s| { + s.suite() == CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 + || s.suite() == CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + }) + .collect(), + ..base + } + }; + let server_config = Arc::new( + rustls::ServerConfig::builder_with_provider(Arc::new(server_provider)) + .with_safe_default_protocol_versions() + .unwrap() + .with_no_client_auth() + .with_single_cert(vec![server_cert.clone()], server_key) + .unwrap(), + ); + + // Given a client using the FIPS provider + let mut root_store = rustls::RootCertStore::empty(); + root_store.add(server_cert).unwrap(); + let client_config = Arc::new( + rustls::ClientConfig::builder_with_provider(Arc::new(fips_provider())) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(root_store) + .with_no_client_auth(), + ); + + let server_name = rustls::pki_types::ServerName::try_from("localhost").unwrap(); + let mut client = rustls::ClientConnection::new(client_config, server_name).unwrap(); + let mut server = rustls::ServerConnection::new(server_config).unwrap(); + + // When the client initiates the TLS handshake and the server attempts + // to negotiate a cipher suite. The handshake is performed entirely in + // memory bytes flow through a Vec buffer rather than a TCP socket, + // since cipher negotiation is a protocol-level concern independent of + // the transport layer. + let mut buf = Vec::new(); + // The client serializes its ClientHello message into the buffer. This + // message contains the list of cipher suites the client supports (only + // AES-GCM suites, since we use the FIPS provider). + client.write_tls(&mut buf).unwrap(); + // The server reads the ClientHello from the buffer and attempts to find + // a cipher suite it shares with the client. Since the server only has + // ChaCha20 and the client only offers AES-GCM, no match is possible. + server.read_tls(&mut &buf[..]).unwrap(); + let result = server.process_new_packets(); + + // Then the handshake fails because there are no shared ciphers + assert!( + matches!( + result, + Err(rustls::Error::PeerIncompatible( + rustls::PeerIncompatible::NoCipherSuitesInCommon + )) + ), + "Expected PeerIncompatible(NoCipherSuitesInCommon), got: {:?}", + result + ); + } +} From 4465b9733bba730fea61d60ef42c799f643936bd Mon Sep 17 00:00:00 2001 From: Jingwei Wang Date: Thu, 28 May 2026 21:53:31 +0000 Subject: [PATCH 2/8] bloodhound: move FIPS checks to all variants with runtime detection The FIPS compliance checks were previously packaged separately and only installed on FIPS-specific variants. This prevented runtime FIPS detection on standard variants where FIPS may be enabled at boot via the kernel flag. Signed-off-by: Jingwei Wang --- packages/os/os.spec | 13 +--- .../bin/bottlerocket-fips-checks/checks.rs | 73 ++++++++++++++++--- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/packages/os/os.spec b/packages/os/os.spec index b748ece8a..d006e3fb0 100644 --- a/packages/os/os.spec +++ b/packages/os/os.spec @@ -400,7 +400,6 @@ Conflicts: %{_cross_os}image-feature(no-host-containers) Summary: Compliance check framework Requires: (%{_cross_os}bloodhound-k8s if %{_cross_os}variant-runtime(k8s)) Requires: (%{_cross_os}bloodhound-k8s-overrides if %{_cross_os}variant-runtime(k8s)) -Requires: (%{_cross_os}bloodhound-fips if %{_cross_os}image-feature(fips)) %description -n %{_cross_os}bloodhound %{summary}. @@ -416,12 +415,6 @@ Requires: (%{_cross_os}bloodhound and %{_cross_os}variant-runtime(k8s)) %description -n %{_cross_os}bloodhound-k8s-overrides %{summary}. -%package -n %{_cross_os}bloodhound-fips -Summary: Compliance checks for FIPS -Requires: (%{_cross_os}bloodhound and %{_cross_os}image-feature(fips)) -%description -n %{_cross_os}bloodhound-fips -%{summary}. - %package -n %{_cross_os}xfscli Summary: XFS progs cli %description -n %{_cross_os}xfscli @@ -979,7 +972,9 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir} %files -n %{_cross_os}bloodhound %{_cross_bindir}/bloodhound %{_cross_bindir}/bottlerocket-cis-checks +%{_cross_bindir}/bottlerocket-fips-checks %{_cross_libexecdir}/cis-checks/bottlerocket +%{_cross_libexecdir}/fips-checks/bottlerocket %exclude %{_cross_libexecdir}/cis-checks/bottlerocket/br03040101.json %files -n %{_cross_os}bloodhound-k8s @@ -990,10 +985,6 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir} %{_cross_libexecdir}/cis-checks/bottlerocket/br03040101.json %{_cross_libexecdir}/cis-checks/kubernetes/k8s04021000.json -%files -n %{_cross_os}bloodhound-fips -%{_cross_bindir}/bottlerocket-fips-checks -%{_cross_libexecdir}/fips-checks/bottlerocket - %files -n %{_cross_os}xfscli %{_cross_sbindir}/xfs_admin %{_cross_sbindir}/xfs_info diff --git a/sources/bloodhound/src/bin/bottlerocket-fips-checks/checks.rs b/sources/bloodhound/src/bin/bottlerocket-fips-checks/checks.rs index 5d0fc809a..d62d2f271 100644 --- a/sources/bloodhound/src/bin/bottlerocket-fips-checks/checks.rs +++ b/sources/bloodhound/src/bin/bottlerocket-fips-checks/checks.rs @@ -11,19 +11,34 @@ const EXPECTED_FIPS_NAME: &str = "Amazon Linux 2023 Kernel Cryptographic API"; const FIPS_KERNEL_CHECK_MARKER: &str = "/etc/.fips-kernel-check-passed"; const FIPS_MODULE_CHECK_MARKER: &str = "/etc/.fips-module-check-passed"; +/// Checks whether kernel FIPS mode is active by reading /proc/sys/crypto/fips_enabled. +fn is_fips_enabled(sac: &dyn SystemAccess) -> bool { + look_for_strings_in_file(sac, CRYPTO_FIPS_ENABLED, &[EXPECTED_FIPS_ENABLED]).unwrap_or(false) +} + +/// Returns a SKIP result indicating FIPS is not enabled on this system. +fn skip_fips_not_enabled() -> CheckerResult { + CheckerResult { + status: CheckStatus::SKIP, + error: "FIPS mode is not enabled on this system".to_string(), + ..Default::default() + } +} + // =>o.o<= =>o.o<= =>o.o<= =>o.o<= =>o.o<= =>o.o<= =>o.o<= =>o.o<= =>o.o<= =>o.o<= pub struct FIPS01000000Checker {} impl Checker for FIPS01000000Checker { fn execute(&self, sac: &dyn SystemAccess) -> CheckerResult { - check_file_contains!( - sac, - CRYPTO_FIPS_ENABLED, - &[EXPECTED_FIPS_ENABLED], - format!("{CRYPTO_FIPS_ENABLED} != {EXPECTED_FIPS_ENABLED}"), - format!("{CRYPTO_FIPS_ENABLED} not found") - ) + if is_fips_enabled(sac) { + CheckerResult { + status: CheckStatus::PASS, + ..Default::default() + } + } else { + skip_fips_not_enabled() + } } fn metadata(&self) -> CheckerMetadata { @@ -43,6 +58,9 @@ pub struct FIPS01010000Checker {} impl Checker for FIPS01010000Checker { fn execute(&self, sac: &dyn SystemAccess) -> CheckerResult { + if !is_fips_enabled(sac) { + return skip_fips_not_enabled(); + } check_file_contains!( sac, CRYPTO_FIPS_NAME, @@ -54,7 +72,7 @@ impl Checker for FIPS01010000Checker { fn metadata(&self) -> CheckerMetadata { CheckerMetadata { - title: format!("FIPS module is {EXPECTED_FIPS_NAME}.").to_string(), + title: format!("FIPS module is {EXPECTED_FIPS_NAME}."), id: "1.1".to_string(), level: 0, name: "fips01010000".to_string(), @@ -69,13 +87,16 @@ pub struct FIPS01020000Checker {} impl Checker for FIPS01020000Checker { fn execute(&self, sac: &dyn SystemAccess) -> CheckerResult { + if !is_fips_enabled(sac) { + return skip_fips_not_enabled(); + } + let result = check_file_exists!( sac, FIPS_KERNEL_CHECK_MARKER, format!("{FIPS_KERNEL_CHECK_MARKER} not found") ); - // Check if we need to continue if result.status == CheckStatus::FAIL { return result; } @@ -118,7 +139,7 @@ mod tests { usac.register_file(CRYPTO_FIPS_ENABLED, "0"); let checker = FIPS01000000Checker {}; let result = checker.execute(&usac); - assert_eq!(result.status, CheckStatus::FAIL); + assert_eq!(result.status, CheckStatus::SKIP); } #[test] pub fn test_fips01000000checker_fips_enabled() { @@ -140,6 +161,7 @@ mod tests { #[test] pub fn test_fips01010000checker_wrong_fips_name() { let mut usac = UnitTestSystemAccess::default(); + usac.register_file(CRYPTO_FIPS_ENABLED, EXPECTED_FIPS_ENABLED); usac.register_file(CRYPTO_FIPS_NAME, "some wrong name"); let checker = FIPS01010000Checker {}; let result = checker.execute(&usac); @@ -149,6 +171,7 @@ mod tests { #[test] pub fn test_fips01010000checker_passing() { let mut usac = UnitTestSystemAccess::default(); + usac.register_file(CRYPTO_FIPS_ENABLED, EXPECTED_FIPS_ENABLED); usac.register_file(CRYPTO_FIPS_NAME, EXPECTED_FIPS_NAME); let checker = FIPS01010000Checker {}; let result = checker.execute(&usac); @@ -158,6 +181,7 @@ mod tests { #[test] pub fn test_fips01020000checker_missing_module_check_marker() { let mut usac = UnitTestSystemAccess::default(); + usac.register_file(CRYPTO_FIPS_ENABLED, EXPECTED_FIPS_ENABLED); usac.register_file(FIPS_KERNEL_CHECK_MARKER, "1"); let checker = FIPS01020000Checker {}; let result = checker.execute(&usac); @@ -166,6 +190,7 @@ mod tests { #[test] pub fn test_fips01020000checker_missing_kernel_check_marker() { let mut usac = UnitTestSystemAccess::default(); + usac.register_file(CRYPTO_FIPS_ENABLED, EXPECTED_FIPS_ENABLED); usac.register_file(FIPS_MODULE_CHECK_MARKER, "1"); let checker = FIPS01020000Checker {}; let result = checker.execute(&usac); @@ -174,10 +199,38 @@ mod tests { #[test] pub fn test_fips01020000checker_passing() { let mut usac = UnitTestSystemAccess::default(); + usac.register_file(CRYPTO_FIPS_ENABLED, EXPECTED_FIPS_ENABLED); usac.register_file(FIPS_KERNEL_CHECK_MARKER, "1"); usac.register_file(FIPS_MODULE_CHECK_MARKER, "1"); let checker = FIPS01020000Checker {}; let result = checker.execute(&usac); assert_eq!(result.status, CheckStatus::PASS); } + + #[test] + pub fn test_is_fips_enabled_true() { + let mut usac = UnitTestSystemAccess::default(); + usac.register_file(CRYPTO_FIPS_ENABLED, EXPECTED_FIPS_ENABLED); + assert!(is_fips_enabled(&usac)); + } + + #[test] + pub fn test_is_fips_enabled_false() { + let mut usac = UnitTestSystemAccess::default(); + usac.register_file(CRYPTO_FIPS_ENABLED, "0"); + assert!(!is_fips_enabled(&usac)); + } + + #[test] + pub fn test_is_fips_enabled_missing_file() { + let usac = UnitTestSystemAccess::default(); + assert!(!is_fips_enabled(&usac)); + } + + #[test] + pub fn test_skip_fips_not_enabled() { + let result = skip_fips_not_enabled(); + assert_eq!(result.status, CheckStatus::SKIP); + assert!(!result.error.is_empty()); + } } From 19983494ae8df32dad639665d323aa50b8ab6023 Mon Sep 17 00:00:00 2001 From: Jingwei Wang Date: Tue, 2 Jun 2026 18:02:59 +0000 Subject: [PATCH 3/8] sources: add aws-smithy-http-client crate Add a local carry of the aws-smithy-http-client crate from smithy-rs with support for custom CryptoProvider injection. This enables Bottlerocket binaries to configure TLS at runtime rather than relying on compile-time feature flags for FIPS crypto selection. This replaces aws-smithy-experimental as the HTTP client abstraction for binaries that need explicit control over TLS configuration. Signed-off-by: Jingwei Wang --- sources/Cargo.lock | 25 +- sources/Cargo.toml | 2 + sources/aws-smithy-http-client/Cargo.toml | 33 + sources/aws-smithy-http-client/LICENSE | 176 +++ sources/aws-smithy-http-client/README.md | 12 + sources/aws-smithy-http-client/src/client.rs | 1287 +++++++++++++++++ .../src/client/connect.rs | 164 +++ .../aws-smithy-http-client/src/client/dns.rs | 41 + .../src/client/proxy.rs | 816 +++++++++++ .../src/client/timeout.rs | 316 ++++ .../aws-smithy-http-client/src/client/tls.rs | 141 ++ .../src/client/tls/rustls_provider.rs | 441 ++++++ sources/aws-smithy-http-client/src/error.rs | 26 + sources/aws-smithy-http-client/src/lib.rs | 72 + 14 files changed, 3551 insertions(+), 1 deletion(-) create mode 100644 sources/aws-smithy-http-client/Cargo.toml create mode 100644 sources/aws-smithy-http-client/LICENSE create mode 100644 sources/aws-smithy-http-client/README.md create mode 100644 sources/aws-smithy-http-client/src/client.rs create mode 100644 sources/aws-smithy-http-client/src/client/connect.rs create mode 100644 sources/aws-smithy-http-client/src/client/dns.rs create mode 100644 sources/aws-smithy-http-client/src/client/proxy.rs create mode 100644 sources/aws-smithy-http-client/src/client/timeout.rs create mode 100644 sources/aws-smithy-http-client/src/client/tls.rs create mode 100644 sources/aws-smithy-http-client/src/client/tls/rustls_provider.rs create mode 100644 sources/aws-smithy-http-client/src/error.rs create mode 100644 sources/aws-smithy-http-client/src/lib.rs diff --git a/sources/Cargo.lock b/sources/Cargo.lock index b483b6571..d73c3943b 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -1095,6 +1095,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "aws-smithy-http-client" +version = "0.1.0" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.4.15", + "http 1.4.2", + "hyper 1.10.1", + "hyper-rustls 0.27.9", + "hyper-util", + "pin-project-lite", + "rustls 0.23.42", + "rustls-native-certs 0.8.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower 0.5.3", + "tracing", + "url", +] + [[package]] name = "aws-smithy-http-client" version = "1.2.0" @@ -1188,7 +1211,7 @@ checksum = "bea94a9ff8464016338c851e24b472d7131c388c88898a502e781815b2ee6045" dependencies = [ "aws-smithy-async", "aws-smithy-http", - "aws-smithy-http-client", + "aws-smithy-http-client 1.2.0", "aws-smithy-observability", "aws-smithy-runtime-api", "aws-smithy-schema", diff --git a/sources/Cargo.toml b/sources/Cargo.toml index 9d120240e..9782adbac 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -85,12 +85,14 @@ members = [ "xfscli", "whippet", + "aws-smithy-http-client", "bottlerocket-crypto-provider", ] [workspace.dependencies] apiclient = { version = "0.1", path = "api/apiclient", default-features = false } aws-smithy-experimental = { version = "0.1", path = "aws-smithy-experimental" } +aws-smithy-http-client = { version = "0.1", path = "aws-smithy-http-client" } block-party = { version = "0.1", path = "updater/block-party" } bottlerocket-image-features = { version = "0.1", path = "bottlerocket-image-features" } bottlerocket-release = { version = "0.1", path = "bottlerocket-release" } diff --git a/sources/aws-smithy-http-client/Cargo.toml b/sources/aws-smithy-http-client/Cargo.toml new file mode 100644 index 000000000..75004241d --- /dev/null +++ b/sources/aws-smithy-http-client/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "aws-smithy-http-client" +version = "0.1.0" +authors = ["AWS Rust SDK Team "] +description = "HTTP client for smithy-rs ecosystem (Bottlerocket carry)" +edition = "2021" +license = "Apache-2.0" +publish = false + +[features] +default-client = ["dep:hyper", "dep:hyper-util", "dep:tower", "dep:rustls-pki-types", "dep:rustls-native-certs"] +rustls-ring = ["dep:rustls", "rustls?/ring", "dep:hyper-rustls", "dep:tokio-rustls", "default-client"] +rustls-aws-lc = ["dep:rustls", "rustls?/aws_lc_rs", "dep:hyper-rustls", "dep:tokio-rustls", "default-client"] +rustls-aws-lc-fips = ["dep:rustls", "rustls?/fips", "dep:hyper-rustls", "dep:tokio-rustls", "default-client"] + +[dependencies] +aws-smithy-async = { workspace = true } +aws-smithy-runtime-api = { workspace = true, features = ["client", "http-1x"] } +aws-smithy-types = { workspace = true, features = ["http-body-1-x"] } +h2 = { workspace = true } +http = "1" +hyper = { workspace = true, features = ["client", "http1", "http2"], optional = true } +hyper-rustls = { workspace = true, features = ["http2", "http1", "native-tokio", "tls12"], default-features = false, optional = true } +hyper-util = { workspace = true, features = ["http1", "http2", "client-legacy", "client-proxy"], optional = true } +pin-project-lite = "0.2" +rustls = { workspace = true, optional = true } +rustls-native-certs = { version = "0.8", optional = true } +rustls-pki-types = { version = "1", features = ["std"], optional = true } +tokio = { workspace = true } +tokio-rustls = { version = "0.26", default-features = false, optional = true } +tower = { version = "0.5", optional = true } +tracing = "0.1" +url = { workspace = true } diff --git a/sources/aws-smithy-http-client/LICENSE b/sources/aws-smithy-http-client/LICENSE new file mode 100644 index 000000000..d9a10c0d8 --- /dev/null +++ b/sources/aws-smithy-http-client/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/sources/aws-smithy-http-client/README.md b/sources/aws-smithy-http-client/README.md new file mode 100644 index 000000000..b97c0f97e --- /dev/null +++ b/sources/aws-smithy-http-client/README.md @@ -0,0 +1,12 @@ +# aws-smithy-http-client + +HTTP client abstractions for generated smithy clients. + +This is a Bottlerocket carry of selected components from the upstream +[aws-smithy-http-client](https://github.com/smithy-lang/smithy-rs) crate, +modified to support custom `CryptoProvider` injection for runtime FIPS +TLS configuration. + + +This crate is part of the [AWS SDK for Rust](https://awslabs.github.io/aws-sdk-rust/) and the [smithy-rs](https://github.com/smithy-lang/smithy-rs) code generator. In most cases, it should not be used directly. + diff --git a/sources/aws-smithy-http-client/src/client.rs b/sources/aws-smithy-http-client/src/client.rs new file mode 100644 index 000000000..75667fa79 --- /dev/null +++ b/sources/aws-smithy-http-client/src/client.rs @@ -0,0 +1,1287 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +mod dns; +/// Proxy configuration +pub mod proxy; +mod timeout; +/// TLS connector(s) +pub mod tls; + +pub(crate) mod connect; + +use crate::cfg::cfg_tls; +use crate::tls::TlsContext; +use aws_smithy_async::future::timeout::TimedOutError; +use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; +use aws_smithy_runtime_api::box_error::BoxError; +use aws_smithy_runtime_api::client::connection::CaptureSmithyConnection; +use aws_smithy_runtime_api::client::connection::ConnectionMetadata; +use aws_smithy_runtime_api::client::connector_metadata::ConnectorMetadata; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpClient, + SharedHttpConnector, +}; +use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; +use aws_smithy_runtime_api::client::result::ConnectorError; +use aws_smithy_runtime_api::client::runtime_components::{ + RuntimeComponents, RuntimeComponentsBuilder, +}; +use aws_smithy_runtime_api::shared::IntoShared; +use aws_smithy_types::body::SdkBody; +use aws_smithy_types::config_bag::ConfigBag; +use aws_smithy_types::error::display::DisplayErrorContext; +use aws_smithy_types::retry::ErrorKind; +use client::connect::Connection; +use h2::Reason; +use http::{Extensions, Uri}; +use hyper::rt::{Read, Write}; +use hyper_util::client::legacy as client; +use hyper_util::client::legacy::connect::dns::GaiResolver; +use hyper_util::client::legacy::connect::{ + capture_connection, CaptureConnection, Connect, HttpConnector as HyperHttpConnector, HttpInfo, +}; +use hyper_util::client::proxy::matcher::Matcher; +use hyper_util::rt::{TokioExecutor, TokioTimer}; +use std::borrow::Cow; +use std::collections::HashMap; +use std::error::Error; +use std::fmt; +use std::sync::RwLock; +use std::time::Duration; + +/// Given `HttpConnectorSettings` and an `SharedAsyncSleep`, create a `SharedHttpConnector` from defaults depending on what cargo features are activated. +pub fn default_connector( + settings: &HttpConnectorSettings, + sleep: Option, +) -> Option { + #[cfg(feature = "rustls-aws-lc")] + { + tracing::trace!(settings = ?settings, sleep = ?sleep, "creating a new default connector"); + let mut conn_builder = Connector::builder().connector_settings(settings.clone()); + + if let Some(sleep) = sleep { + conn_builder = conn_builder.sleep_impl(sleep); + } + + let conn = conn_builder + .tls_provider(tls::Provider::Rustls( + tls::rustls_provider::CryptoMode::AwsLc, + )) + .build(); + Some(SharedHttpConnector::new(conn)) + } + #[cfg(not(feature = "rustls-aws-lc"))] + { + tracing::trace!(settings = ?settings, sleep = ?sleep, "no default connector available"); + None + } +} + +/// [`HttpConnector`] used to make HTTP requests. +/// +/// This connector also implements socket connect and read timeouts. +/// +/// This shouldn't be used directly in most cases. +/// See the docs on [`Builder`] for examples of how to customize the HTTP client. +#[derive(Debug)] +pub struct Connector { + adapter: Box, +} + +impl Connector { + /// Builder for an HTTP connector. + pub fn builder() -> ConnectorBuilder { + ConnectorBuilder { + enable_tcp_nodelay: true, + ..Default::default() + } + } +} + +impl HttpConnector for Connector { + fn call(&self, request: HttpRequest) -> HttpConnectorFuture { + self.adapter.call(request) + } +} + +/// Builder for [`Connector`]. +#[derive(Default, Debug, Clone)] +pub struct ConnectorBuilder { + connector_settings: Option, + sleep_impl: Option, + client_builder: Option, + pool_idle_timeout: Option>, + enable_tcp_nodelay: bool, + interface: Option, + proxy_config: Option, + #[allow(unused)] + tls: Tls, +} + +/// Initial builder state, `TlsProvider` choice required +#[derive(Default, Debug, Clone)] +#[non_exhaustive] +pub struct TlsUnset {} + +/// TLS implementation selected +#[derive(Debug, Clone)] +pub struct TlsProviderSelected { + #[allow(unused)] + provider: tls::Provider, + #[allow(unused)] + context: TlsContext, +} + +impl ConnectorBuilder { + /// Set the TLS implementation to use for this connector + pub fn tls_provider(self, provider: tls::Provider) -> ConnectorBuilder { + ConnectorBuilder { + connector_settings: self.connector_settings, + sleep_impl: self.sleep_impl, + client_builder: self.client_builder, + enable_tcp_nodelay: self.enable_tcp_nodelay, + interface: self.interface, + proxy_config: self.proxy_config, + pool_idle_timeout: self.pool_idle_timeout, + tls: TlsProviderSelected { + provider, + context: TlsContext::default(), + }, + } + } + + /// Build an HTTP connector sans TLS + #[doc(hidden)] + pub fn build_http(self) -> Connector { + if let Some(ref proxy_config) = self.proxy_config { + if proxy_config.requires_tls() { + tracing::warn!( + "HTTPS proxy configured but no TLS provider set. \ + Connections to HTTPS proxy servers will fail. \ + Consider configuring a TLS provider to enable TLS support." + ); + } + } + + let base = self.base_connector(); + + // Wrap with HTTP proxy support if proxy is configured + let proxy_config = self + .proxy_config + .clone() + .unwrap_or_else(proxy::ProxyConfig::disabled); + + if !proxy_config.is_disabled() { + let http_proxy_connector = connect::HttpProxyConnector::new(base, proxy_config); + self.wrap_connector(http_proxy_connector) + } else { + self.wrap_connector(base) + } + } +} + +impl ConnectorBuilder { + /// Create a [`Connector`] from this builder and a given connector. + pub(crate) fn wrap_connector(self, tcp_connector: C) -> Connector + where + C: Send + Sync + 'static, + C: Clone, + C: tower::Service, + C::Response: Read + Write + Connection + Send + Sync + Unpin, + C: Connect, + C::Future: Unpin + Send + 'static, + C::Error: Into, + { + let client_builder = self + .client_builder + .unwrap_or_else(|| new_tokio_hyper_builder(self.pool_idle_timeout)); + let sleep_impl = self.sleep_impl.or_else(default_async_sleep); + let (connect_timeout, read_timeout) = self + .connector_settings + .map(|c| (c.connect_timeout(), c.read_timeout())) + .unwrap_or((None, None)); + + let connector = match connect_timeout { + Some(duration) => timeout::ConnectTimeout::new( + tcp_connector, + sleep_impl + .clone() + .expect("a sleep impl must be provided in order to have a connect timeout"), + duration, + ), + None => timeout::ConnectTimeout::no_timeout(tcp_connector), + }; + let base = client_builder.build(connector); + let read_timeout = match read_timeout { + Some(duration) => timeout::HttpReadTimeout::new( + base, + sleep_impl.expect("a sleep impl must be provided in order to have a read timeout"), + duration, + ), + None => timeout::HttpReadTimeout::no_timeout(base), + }; + + let proxy_matcher = self + .proxy_config + .as_ref() + .map(|config| config.clone().into_hyper_util_matcher()); + + Connector { + adapter: Box::new(Adapter { + client: read_timeout, + proxy_matcher, + }), + } + } + + /// Get the base TCP connector by mapping our config to the underlying `HttpConnector` from hyper + /// (which is a base TCP connector with no TLS or any wrapping) + fn base_connector(&self) -> HyperHttpConnector { + self.base_connector_with_resolver(GaiResolver::new()) + } + + /// Get the base TCP connector by mapping our config to the underlying `HttpConnector` from hyper + /// using the given resolver `R` + fn base_connector_with_resolver(&self, resolver: R) -> HyperHttpConnector { + let mut conn = HyperHttpConnector::new_with_resolver(resolver); + conn.set_nodelay(self.enable_tcp_nodelay); + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + if let Some(interface) = &self.interface { + conn.set_interface(interface); + } + conn + } + + /// Set the async sleep implementation used for timeouts + /// + /// Calling this is only necessary for testing or to use something other than + /// [`default_async_sleep`]. + pub fn sleep_impl(mut self, sleep_impl: impl AsyncSleep + 'static) -> Self { + self.sleep_impl = Some(sleep_impl.into_shared()); + self + } + + /// Set the async sleep implementation used for timeouts + /// + /// Calling this is only necessary for testing or to use something other than + /// [`default_async_sleep`]. + pub fn set_sleep_impl(&mut self, sleep_impl: Option) -> &mut Self { + self.sleep_impl = sleep_impl; + self + } + + /// Configure the HTTP settings for the `HyperAdapter` + pub fn connector_settings(mut self, connector_settings: HttpConnectorSettings) -> Self { + self.connector_settings = Some(connector_settings); + self + } + + /// Configure the HTTP settings for the `HyperAdapter` + pub fn set_connector_settings( + &mut self, + connector_settings: Option, + ) -> &mut Self { + self.connector_settings = connector_settings; + self + } + + /// Configure `SO_NODELAY` for all sockets to the supplied value `nodelay` + pub fn enable_tcp_nodelay(mut self, nodelay: bool) -> Self { + self.enable_tcp_nodelay = nodelay; + self + } + + /// Configure `SO_NODELAY` for all sockets to the supplied value `nodelay` + pub fn set_enable_tcp_nodelay(&mut self, nodelay: bool) -> &mut Self { + self.enable_tcp_nodelay = nodelay; + self + } + + /// Sets the value for the `SO_BINDTODEVICE` option on this socket. + /// + /// If a socket is bound to an interface, only packets received from that particular + /// interface are processed by the socket. Note that this only works for some socket + /// types (e.g. `AF_INET` sockets). + /// + /// On Linux it can be used to specify a [VRF], but the binary needs to either have + /// `CAP_NET_RAW` capability set or be run as root. + /// + /// This function is only available on Android, Fuchsia, and Linux. + /// + /// [VRF]: https://www.kernel.org/doc/Documentation/networking/vrf.txt + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + pub fn set_interface>(&mut self, interface: S) -> &mut Self { + self.interface = Some(interface.into()); + self + } + + /// Configure proxy settings for this connector + /// + /// This method allows you to set explicit proxy configuration for the HTTP client. + /// The proxy configuration will be used to determine whether requests should be + /// routed through a proxy server or connect directly. + /// + /// # Examples + /// + /// ```rust + /// # #[cfg(feature = "rustls-aws-lc")] + /// # { + /// use aws_smithy_http_client::{Connector, proxy::ProxyConfig, tls}; + /// + /// let proxy_config = ProxyConfig::http("http://proxy.example.com:8080")?; + /// let connector = Connector::builder() + /// .proxy_config(proxy_config) + /// .tls_provider(tls::Provider::Rustls(tls::rustls_provider::CryptoMode::AwsLc)) + /// .build(); + /// # } + /// # Ok::<(), Box>(()) + /// ``` + pub fn proxy_config(mut self, config: proxy::ProxyConfig) -> Self { + self.proxy_config = Some(config); + self + } + + /// Configure proxy settings for this connector + /// + /// This is the mutable version of [`proxy_config`](Self::proxy_config). + pub fn set_proxy_config(&mut self, config: Option) -> &mut Self { + self.proxy_config = config; + self + } + + /// Set an optional timeout for idle sockets being kept-alive. + /// + /// Pass `None` to disable timeout. + /// + /// Defaults to Hyper's default timeout, which is currently 90 seconds - see + /// [hyper_util::client::legacy::Builder::pool_idle_timeout], + /// but unlike that function, there is no need to call `pool_timer` yourself. + /// + /// # Examples + /// + /// ```rust + /// # #[cfg(feature = "rustls-aws-lc")] + /// # { + /// use aws_smithy_http_client::{Connector, tls}; + /// use std::time::Duration; + /// + /// let connector = Connector::builder() + /// .pool_idle_timeout(Duration::from_secs(30)) + /// .tls_provider(tls::Provider::Rustls(tls::rustls_provider::CryptoMode::AwsLc)) + /// .build(); + /// # } + /// # Ok::<(), Box>(()) + /// ``` + pub fn pool_idle_timeout(mut self, val: D) -> Self + where + D: Into>, + { + self.pool_idle_timeout = Some(val.into()); + self + } + + /// Set an optional timeout for idle sockets being kept-alive. + /// + /// Pass `None` to use Hyper's default timeout, `Some(None)` to disable timeouts. + /// + /// This is the mutable version of [`pool_idle_timeout`](Self::pool_idle_timeout). + /// + /// # Examples + /// + /// ```rust + /// # #[cfg(feature = "rustls-aws-lc")] + /// # { + /// use aws_smithy_http_client::{Connector, tls}; + /// use std::time::Duration; + /// + /// let mut connector = Connector::builder(); + /// connector + /// .set_pool_idle_timeout(Some(Some(Duration::from_secs(30)))); + /// connector + /// .tls_provider(tls::Provider::Rustls(tls::rustls_provider::CryptoMode::AwsLc)) + /// .build(); + /// # } + /// # Ok::<(), Box>(()) + /// ``` + pub fn set_pool_idle_timeout(&mut self, val: Option>) -> &mut Self { + self.pool_idle_timeout = val; + self + } + + /// Override the Hyper client [`Builder`](hyper_util::client::legacy::Builder) used to construct this client. + /// + /// This enables changing settings like forcing HTTP2 and modifying other default client behavior. + pub(crate) fn hyper_builder( + mut self, + hyper_builder: hyper_util::client::legacy::Builder, + ) -> Self { + self.set_hyper_builder(Some(hyper_builder)); + self + } + + /// Override the Hyper client [`Builder`](hyper_util::client::legacy::Builder) used to construct this client. + /// + /// This enables changing settings like forcing HTTP2 and modifying other default client behavior. + pub(crate) fn set_hyper_builder( + &mut self, + hyper_builder: Option, + ) -> &mut Self { + self.client_builder = hyper_builder; + self + } +} + +/// Adapter to use a Hyper 1.0-based Client as an `HttpConnector` +/// +/// This adapter also enables TCP `CONNECT` and HTTP `READ` timeouts via [`Connector::builder`]. +struct Adapter { + client: timeout::HttpReadTimeout< + hyper_util::client::legacy::Client, SdkBody>, + >, + proxy_matcher: Option, +} + +impl fmt::Debug for Adapter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Adapter") + .field("client", &"** hyper client **") + .field("proxy_matcher", &self.proxy_matcher.is_some()) + .finish() + } +} + +/// Extract a smithy connection from a hyper CaptureConnection +fn extract_smithy_connection(capture_conn: &CaptureConnection) -> Option { + let capture_conn = capture_conn.clone(); + if let Some(conn) = capture_conn.clone().connection_metadata().as_ref() { + let mut extensions = Extensions::new(); + conn.get_extras(&mut extensions); + let http_info = extensions.get::(); + let mut builder = ConnectionMetadata::builder() + .proxied(conn.is_proxied()) + .poison_fn(move || match capture_conn.connection_metadata().as_ref() { + Some(conn) => conn.poison(), + None => tracing::trace!("no connection existed to poison"), + }); + + builder + .set_local_addr(http_info.map(|info| info.local_addr())) + .set_remote_addr(http_info.map(|info| info.remote_addr())); + + let smithy_connection = builder.build(); + + Some(smithy_connection) + } else { + None + } +} + +fn new_tokio_hyper_builder( + pool_idle_timeout: Option>, +) -> hyper_util::client::legacy::Builder { + let mut builder = hyper_util::client::legacy::Builder::new(TokioExecutor::new()); + // Explicitly setting the pool_timer is required for connection timeouts to work. + builder.pool_timer(TokioTimer::new()); + + if let Some(pool_idle_timeout) = pool_idle_timeout { + builder.pool_idle_timeout(pool_idle_timeout); + } + + builder +} + +impl Adapter { + /// Add proxy authentication header to the request if needed + fn add_proxy_auth_header(&self, request: &mut http::Request) { + // Only add auth for HTTP requests (not HTTPS which uses CONNECT tunneling) + if request.uri().scheme() != Some(&http::uri::Scheme::HTTP) { + return; + } + + // Don't override existing proxy authorization header + if request + .headers() + .contains_key(http::header::PROXY_AUTHORIZATION) + { + return; + } + + if let Some(ref matcher) = self.proxy_matcher { + if let Some(intercept) = matcher.intercept(request.uri()) { + // Add basic auth header if available + if let Some(auth_header) = intercept.basic_auth() { + request + .headers_mut() + .insert(http::header::PROXY_AUTHORIZATION, auth_header.clone()); + tracing::debug!("added proxy authentication header for {}", request.uri()); + } + } + } + } +} + +impl HttpConnector for Adapter +where + C: Clone + Send + Sync + 'static, + C: tower::Service, + C::Response: Connection + Read + Write + Unpin + 'static, + timeout::ConnectTimeout: Connect, + C::Future: Unpin + Send + 'static, + C::Error: Into, +{ + fn call(&self, request: HttpRequest) -> HttpConnectorFuture { + let mut request = match request.try_into_http1x() { + Ok(request) => request, + Err(err) => { + return HttpConnectorFuture::ready(Err(ConnectorError::user(err.into()))); + } + }; + + self.add_proxy_auth_header(&mut request); + + let capture_connection = capture_connection(&mut request); + if let Some(capture_smithy_connection) = + request.extensions().get::() + { + capture_smithy_connection + .set_connection_retriever(move || extract_smithy_connection(&capture_connection)); + } + let mut client = self.client.clone(); + use tower::Service; + let fut = client.call(request); + HttpConnectorFuture::new(async move { + let response = fut + .await + .map_err(downcast_error)? + .map(SdkBody::from_body_1_x); + match HttpResponse::try_from(response) { + Ok(response) => Ok(response), + Err(err) => Err(ConnectorError::other(err.into(), None)), + } + }) + } +} + +/// Downcast errors coming out of hyper into an appropriate `ConnectorError` +fn downcast_error(err: BoxError) -> ConnectorError { + // is a `TimedOutError` (from aws_smithy_async::timeout) in the chain? if it is, this is a timeout + if find_source::(err.as_ref()).is_some() { + return ConnectorError::timeout(err); + } + // is the top of chain error actually already a `ConnectorError`? return that directly + let err = match err.downcast::() { + Ok(connector_error) => return *connector_error, + Err(box_error) => box_error, + }; + // generally, the top of chain will probably be a hyper error. Go through a set of hyper specific + // error classifications + let err = match find_source::(err.as_ref()) { + Some(hyper_error) => return to_connector_error(hyper_error)(err), + None => match find_source::(err.as_ref()) { + Some(hyper_util_err) => { + if hyper_util_err.is_connect() + || find_source::(hyper_util_err).is_some() + { + return ConnectorError::io(err); + } + err + } + None => err, + }, + }; + + // otherwise, we have no idea! + ConnectorError::other(err, None) +} + +/// Convert a [`hyper::Error`] into a [`ConnectorError`] +fn to_connector_error(err: &hyper::Error) -> fn(BoxError) -> ConnectorError { + if err.is_timeout() || find_source::(err).is_some() { + return ConnectorError::timeout; + } + if err.is_user() { + return ConnectorError::user; + } + if err.is_closed() || err.is_canceled() || find_source::(err).is_some() { + return ConnectorError::io; + } + // We sometimes receive this from S3: hyper::Error(IncompleteMessage) + if err.is_incomplete_message() { + return |err: BoxError| ConnectorError::other(err, Some(ErrorKind::TransientError)); + } + + if let Some(h2_err) = find_source::(err) { + if h2_err.is_go_away() + || (h2_err.is_reset() && h2_err.reason() == Some(Reason::REFUSED_STREAM)) + { + return ConnectorError::io; + } + } + + tracing::warn!(err = %DisplayErrorContext(&err), "unrecognized error from Hyper. If this error should be retried, please file an issue."); + |err: BoxError| ConnectorError::other(err, None) +} + +fn find_source<'a, E: Error + 'static>(err: &'a (dyn Error + 'static)) -> Option<&'a E> { + let mut next = Some(err); + while let Some(err) = next { + if let Some(matching_err) = err.downcast_ref::() { + return Some(matching_err); + } + next = err.source(); + } + None +} + +// TODO(https://github.com/awslabs/aws-sdk-rust/issues/1090): CacheKey must also include ptr equality to any +// runtime components that are used—sleep_impl as a base (unless we prohibit overriding sleep impl) +// If we decide to put a DnsResolver in RuntimeComponents, then we'll need to handle that as well. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +struct CacheKey { + connect_timeout: Option, + read_timeout: Option, +} + +impl From<&HttpConnectorSettings> for CacheKey { + fn from(value: &HttpConnectorSettings) -> Self { + Self { + connect_timeout: value.connect_timeout(), + read_timeout: value.read_timeout(), + } + } +} + +struct HyperClient { + connector_cache: RwLock>, + client_builder: hyper_util::client::legacy::Builder, + connector_fn: F, +} + +impl fmt::Debug for HyperClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HyperClient") + .field("connector_cache", &self.connector_cache) + .field("client_builder", &self.client_builder) + .finish() + } +} + +impl HttpClient for HyperClient +where + F: Fn( + hyper_util::client::legacy::Builder, + Option<&HttpConnectorSettings>, + Option<&RuntimeComponents>, + ) -> Connector + + Send + + Sync + + 'static, +{ + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector { + let key = CacheKey::from(settings); + let mut connector = self.connector_cache.read().unwrap().get(&key).cloned(); + if connector.is_none() { + let mut cache = self.connector_cache.write().unwrap(); + // Short-circuit if another thread already wrote a connector to the cache for this key + if !cache.contains_key(&key) { + let start = components.time_source().map(|ts| ts.now()); + let connector = (self.connector_fn)( + self.client_builder.clone(), + Some(settings), + Some(components), + ); + let end = components.time_source().map(|ts| ts.now()); + if let (Some(start), Some(end)) = (start, end) { + if let Ok(elapsed) = end.duration_since(start) { + tracing::debug!("new connector created in {:?}", elapsed); + } + } + let connector = SharedHttpConnector::new(connector); + cache.insert(key.clone(), connector); + } + connector = cache.get(&key).cloned(); + } + + connector.expect("cache populated above") + } + + fn validate_base_client_config( + &self, + _: &RuntimeComponentsBuilder, + _: &ConfigBag, + ) -> Result<(), BoxError> { + // Initialize the TCP connector at this point so that native certs load + // at client initialization time instead of upon first request. We do it + // here rather than at construction so that it won't run if this is not + // the selected HTTP client for the base config (for example, if this was + // the default HTTP client, and it was overridden by a later plugin). + let _ = (self.connector_fn)(self.client_builder.clone(), None, None); + Ok(()) + } + + fn connector_metadata(&self) -> Option { + Some(ConnectorMetadata::new("hyper", Some(Cow::Borrowed("1.x")))) + } +} + +/// Builder for a hyper-backed [`HttpClient`] implementation. +/// +/// This builder can be used to customize the underlying TCP connector used, as well as +/// hyper client configuration. +/// +/// # Examples +/// +/// Construct a Hyper client with the RusTLS TLS implementation. +/// This can be useful when you want to share a Hyper connector between multiple +/// generated Smithy clients. +#[derive(Clone, Default, Debug)] +pub struct Builder { + client_builder: Option, + pool_idle_timeout: Option>, + #[allow(unused)] + tls_provider: Tls, +} + +cfg_tls! { + use aws_smithy_runtime_api::client::dns::ResolveDns; + + impl ConnectorBuilder { + /// Build a [`Connector`] that will use the default DNS resolver implementation. + pub fn build(self) -> Connector { + let http_connector = self.base_connector(); + self.build_https(http_connector) + } + + /// Configure the TLS context + pub fn tls_context(mut self, ctx: TlsContext) -> Self { + self.tls.context = ctx; + self + } + + /// Configure the TLS context + pub fn set_tls_context(&mut self, ctx: TlsContext) -> &mut Self { + self.tls.context = ctx; + self + } + + /// Build a [`Connector`] that will use the given DNS resolver implementation. + pub fn build_with_resolver(self, resolver: R) -> Connector { + use crate::client::dns::HyperUtilResolver; + let http_connector = self.base_connector_with_resolver(HyperUtilResolver { resolver }); + self.build_https(http_connector) + } + + fn build_https(self, http_connector: HyperHttpConnector) -> Connector + where + R: Clone + Send + Sync + 'static, + R: tower::Service, + R::Response: Iterator, + R::Future: Send, + R::Error: Into>, + { + match &self.tls.provider { + // TODO(hyper1) - fix cfg_rustls! to allow matching on patterns so we can re-use it and not duplicate these cfg matches everywhere + #[cfg(any( + feature = "rustls-aws-lc", + feature = "rustls-aws-lc-fips", + feature = "rustls-ring" + ))] + tls::Provider::Rustls(crypto_mode) => { + let proxy_config = self.proxy_config.clone() + .unwrap_or_else(proxy::ProxyConfig::disabled); + + let https_connector = tls::rustls_provider::build_connector::wrap_connector( + http_connector, + crypto_mode.clone(), + &self.tls.context, + proxy_config, + ); + self.wrap_connector(https_connector) + }, + } + } + } + + impl Builder { + /// Create an HTTPS client with the selected TLS provider. + /// + /// The trusted certificates will be loaded later when this becomes the selected + /// HTTP client for a Smithy client. + pub fn build_https(self) -> SharedHttpClient { + build_with_conn_fn( + self.client_builder, + self.pool_idle_timeout, + move |client_builder, settings, runtime_components| { + let builder = new_conn_builder(client_builder, settings, runtime_components) + .tls_provider(self.tls_provider.provider.clone()) + .tls_context(self.tls_provider.context.clone()); + builder.build() + }, + ) + } + + /// Create an HTTPS client using a custom DNS resolver + pub fn build_with_resolver( + self, + resolver: impl ResolveDns + Clone + 'static, + ) -> SharedHttpClient { + build_with_conn_fn( + self.client_builder, + self.pool_idle_timeout, + move |client_builder, settings, runtime_components| { + let builder = new_conn_builder(client_builder, settings, runtime_components) + .tls_provider(self.tls_provider.provider.clone()) + .tls_context(self.tls_provider.context.clone()); + builder.build_with_resolver(resolver.clone()) + }, + ) + } + + /// Configure the TLS context + pub fn tls_context(mut self, ctx: TlsContext) -> Self { + self.tls_provider.context = ctx; + self + } + } +} + +impl Builder { + /// Set an optional timeout for idle sockets being kept-alive. + /// + /// Pass `None` to disable timeout. + /// + /// Defaults to Hyper's default timeout, which is currently 90 seconds - see + /// [hyper_util::client::legacy::Builder::pool_idle_timeout], + /// but unlike that function, there is no need to call `pool_timer` yourself. + /// + /// # Examples + /// + /// ```rust + /// # #[cfg(feature = "rustls-aws-lc")] + /// # { + /// use aws_smithy_http_client::{Builder, tls}; + /// use std::time::Duration; + /// + /// let client = Builder::new() + /// .pool_idle_timeout(Duration::from_secs(30)) + /// .tls_provider(tls::Provider::Rustls(tls::rustls_provider::CryptoMode::AwsLc)) + /// .build_https(); + /// # } + /// # Ok::<(), Box>(()) + /// ``` + pub fn pool_idle_timeout(mut self, val: D) -> Self + where + D: Into>, + { + self.pool_idle_timeout = Some(val.into()); + self + } + + /// Set an optional timeout for idle sockets being kept-alive. + /// + /// Pass `None` to use Hyper's default timeout, `Some(None)` to disable timeouts. + /// + /// This is the mutable version of [`pool_idle_timeout`](Self::pool_idle_timeout). + /// + /// # Examples + /// + /// ```rust + /// # #[cfg(feature = "rustls-aws-lc")] + /// # { + /// use std::time::Duration; + /// use aws_smithy_http_client::{Builder, tls}; + /// + /// let mut client = Builder::new(); + /// client.set_pool_idle_timeout(Some(Some(Duration::from_secs(30)))); + /// client + /// .tls_provider(tls::Provider::Rustls(tls::rustls_provider::CryptoMode::AwsLc)) + /// .build_https(); + /// # } + /// # Ok::<(), Box>(()) + /// ``` + pub fn set_pool_idle_timeout(&mut self, val: Option>) -> &mut Self { + self.pool_idle_timeout = val; + self + } +} + +impl Builder { + /// Creates a new builder. + pub fn new() -> Self { + Self::default() + } + + /// Returns a [`SharedHttpClient`] that calls the given `connector` function to select an HTTP(S) connector. + #[doc(hidden)] + pub fn build_with_connector_fn(self, connector_fn: F) -> SharedHttpClient + where + F: Fn(Option<&HttpConnectorSettings>, Option<&RuntimeComponents>) -> Connector + + Send + + Sync + + 'static, + { + build_with_conn_fn( + self.client_builder, + self.pool_idle_timeout, + move |_builder, settings, runtime_components| { + connector_fn(settings, runtime_components) + }, + ) + } + + /// Build a new HTTP client without TLS enabled + #[doc(hidden)] + pub fn build_http(self) -> SharedHttpClient { + build_with_conn_fn( + self.client_builder, + self.pool_idle_timeout, + move |client_builder, settings, runtime_components| { + let builder = new_conn_builder(client_builder, settings, runtime_components); + builder.build_http() + }, + ) + } + + /// Set the TLS implementation to use + pub fn tls_provider(self, provider: tls::Provider) -> Builder { + Builder { + client_builder: self.client_builder, + pool_idle_timeout: self.pool_idle_timeout, + tls_provider: TlsProviderSelected { + provider, + context: TlsContext::default(), + }, + } + } +} + +pub(crate) fn build_with_conn_fn( + client_builder: Option, + pool_idle_timeout: Option>, + connector_fn: F, +) -> SharedHttpClient +where + F: Fn( + hyper_util::client::legacy::Builder, + Option<&HttpConnectorSettings>, + Option<&RuntimeComponents>, + ) -> Connector + + Send + + Sync + + 'static, +{ + let client_builder = + client_builder.unwrap_or_else(|| new_tokio_hyper_builder(pool_idle_timeout)); + SharedHttpClient::new(HyperClient { + connector_cache: RwLock::new(HashMap::new()), + client_builder, + connector_fn, + }) +} + +#[allow(dead_code)] +pub(crate) fn build_with_tcp_conn_fn( + client_builder: Option, + pool_idle_timeout: Option>, + tcp_connector_fn: F, +) -> SharedHttpClient +where + F: Fn() -> C + Send + Sync + 'static, + C: Clone + Send + Sync + 'static, + C: tower::Service, + C::Response: Connection + Read + Write + Send + Sync + Unpin + 'static, + C::Future: Unpin + Send + 'static, + C::Error: Into, + C: Connect, +{ + build_with_conn_fn( + client_builder, + pool_idle_timeout, + move |client_builder, settings, runtime_components| { + let builder = new_conn_builder(client_builder, settings, runtime_components); + builder.wrap_connector(tcp_connector_fn()) + }, + ) +} + +fn new_conn_builder( + client_builder: hyper_util::client::legacy::Builder, + settings: Option<&HttpConnectorSettings>, + runtime_components: Option<&RuntimeComponents>, +) -> ConnectorBuilder { + let mut builder = Connector::builder().hyper_builder(client_builder); + builder.set_connector_settings(settings.cloned()); + if let Some(components) = runtime_components { + builder.set_sleep_impl(components.sleep_impl()); + } + builder +} + +#[cfg(test)] +mod test { + use std::io::{Error, ErrorKind}; + use std::pin::Pin; + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::Arc; + use std::task::{Context, Poll}; + + use crate::client::timeout::test::NeverConnects; + use aws_smithy_async::assert_elapsed; + use aws_smithy_async::rt::sleep::TokioSleep; + use aws_smithy_async::time::SystemTimeSource; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; + use http::Uri; + use hyper::rt::ReadBufCursor; + use hyper_util::client::legacy::connect::Connected; + + use super::*; + + #[tokio::test] + async fn connector_selection() { + // Create a client that increments a count every time it creates a new Connector + let creation_count = Arc::new(AtomicU32::new(0)); + let http_client = build_with_tcp_conn_fn(None, None, { + let count = creation_count.clone(); + move || { + count.fetch_add(1, Ordering::Relaxed); + NeverConnects + } + }); + + // This configuration should result in 4 separate connectors with different timeout settings + let settings = [ + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .read_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(3)) + .read_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(5)) + .read_timeout(Duration::from_secs(3)) + .build(), + ]; + + // Kick off thousands of parallel tasks that will try to create a connector + let components = RuntimeComponentsBuilder::for_tests() + .with_time_source(Some(SystemTimeSource::new())) + .build() + .unwrap(); + let mut handles = Vec::new(); + for setting in &settings { + for _ in 0..1000 { + let client = http_client.clone(); + handles.push(tokio::spawn({ + let setting = setting.clone(); + let components = components.clone(); + async move { + let _ = client.http_connector(&setting, &components); + } + })); + } + } + for handle in handles { + handle.await.unwrap(); + } + + // Verify only 4 connectors were created amidst the chaos + assert_eq!(4, creation_count.load(Ordering::Relaxed)); + } + + #[tokio::test] + async fn hyper_io_error() { + let connector = TestConnection { + inner: HangupStream, + }; + let adapter = Connector::builder().wrap_connector(connector).adapter; + let err = adapter + .call(HttpRequest::get("https://socket-hangup.com").unwrap()) + .await + .expect_err("socket hangup"); + assert!(err.is_io(), "unexpected error type: {:?}", err); + } + + // ---- machinery to make a Hyper connector that responds with an IO Error + #[derive(Clone)] + struct HangupStream; + + impl Connection for HangupStream { + fn connected(&self) -> Connected { + Connected::new() + } + } + + impl Read for HangupStream { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: ReadBufCursor<'_>, + ) -> Poll> { + Poll::Ready(Err(Error::new( + ErrorKind::ConnectionReset, + "connection reset", + ))) + } + } + + impl Write for HangupStream { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &[u8], + ) -> Poll> { + Poll::Pending + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Pending + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Pending + } + } + + #[derive(Clone)] + struct TestConnection { + inner: T, + } + + impl tower::Service for TestConnection + where + T: Clone + Connection, + { + type Response = T; + type Error = BoxError; + type Future = std::future::Ready>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _req: Uri) -> Self::Future { + std::future::ready(Ok(self.inner.clone())) + } + } + + #[tokio::test] + async fn http_connect_timeout_works() { + let tcp_connector = NeverConnects::default(); + let connector_settings = HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(1)) + .build(); + let hyper = Connector::builder() + .connector_settings(connector_settings) + .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) + .wrap_connector(tcp_connector) + .adapter; + let now = tokio::time::Instant::now(); + tokio::time::pause(); + let resp = hyper + .call(HttpRequest::get("https://static-uri.com").unwrap()) + .await + .unwrap_err(); + assert!( + resp.is_timeout(), + "expected resp.is_timeout() to be true but it was false, resp == {:?}", + resp + ); + let message = DisplayErrorContext(&resp).to_string(); + let expected = "timeout: client error (Connect): HTTP connect timeout occurred after 1s"; + assert!( + message.contains(expected), + "expected '{message}' to contain '{expected}'" + ); + assert_elapsed!(now, Duration::from_secs(1)); + } + + #[tokio::test] + async fn http_read_timeout_works() { + let tcp_connector = crate::client::timeout::test::NeverReplies; + let connector_settings = HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(1)) + .read_timeout(Duration::from_secs(2)) + .build(); + let hyper = Connector::builder() + .connector_settings(connector_settings) + .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) + .wrap_connector(tcp_connector) + .adapter; + let now = tokio::time::Instant::now(); + tokio::time::pause(); + let err = hyper + .call(HttpRequest::get("https://fake-uri.com").unwrap()) + .await + .unwrap_err(); + assert!( + err.is_timeout(), + "expected err.is_timeout() to be true but it was false, err == {err:?}", + ); + let message = format!("{}", DisplayErrorContext(&err)); + let expected = "timeout: HTTP read timeout occurred after 2s"; + assert!( + message.contains(expected), + "expected '{message}' to contain '{expected}'" + ); + assert_elapsed!(now, Duration::from_secs(2)); + } + + #[cfg(not(windows))] + #[tokio::test] + async fn connection_refused_works() { + use crate::client::dns::HyperUtilResolver; + use aws_smithy_runtime_api::client::dns::{DnsFuture, ResolveDns}; + use std::net::{IpAddr, Ipv4Addr}; + + #[derive(Debug, Clone, Default)] + struct TestResolver; + impl ResolveDns for TestResolver { + fn resolve_dns<'a>(&'a self, _name: &'a str) -> DnsFuture<'a> { + let localhost_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + DnsFuture::ready(Ok(vec![localhost_v4])) + } + } + + let connector_settings = HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(20)) + .build(); + + let resolver = HyperUtilResolver { + resolver: TestResolver, + }; + let connector = Connector::builder().base_connector_with_resolver(resolver); + + let hyper = Connector::builder() + .connector_settings(connector_settings) + .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) + .wrap_connector(connector) + .adapter; + + let resp = hyper + .call(HttpRequest::get("http://static-uri:50227.com").unwrap()) + .await + .unwrap_err(); + assert!( + resp.is_io(), + "expected resp.is_io() to be true but it was false, resp == {:?}", + resp + ); + let message = DisplayErrorContext(&resp).to_string(); + let expected = "Connection refused"; + assert!( + message.contains(expected), + "expected '{message}' to contain '{expected}'" + ); + } +} diff --git a/sources/aws-smithy-http-client/src/client/connect.rs b/sources/aws-smithy-http-client/src/client/connect.rs new file mode 100644 index 000000000..653088889 --- /dev/null +++ b/sources/aws-smithy-http-client/src/client/connect.rs @@ -0,0 +1,164 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +use crate::client::connect; +use crate::proxy; +use aws_smithy_runtime_api::box_error::BoxError; +use http::Uri; +use hyper::rt::{Read, ReadBufCursor, Write}; +use hyper_util::client::legacy::connect::{Connected, Connection}; +use pin_project_lite::pin_project; +use std::future::Future; +use std::io; +use std::io::IoSlice; +use std::pin::Pin; +use std::task::{Context, Poll}; + +pub(crate) trait AsyncConn: + Read + Write + Connection + Send + Sync + Unpin + 'static +{ +} + +impl AsyncConn for T {} + +pub(crate) type BoxConn = Box; + +// Future for connecting +pub(crate) type Connecting = Pin> + Send>>; + +pin_project! { + pub(crate) struct Conn { + #[pin] + pub(super)inner: BoxConn, + pub(super) is_proxy: bool, + } +} + +impl Connection for Conn { + fn connected(&self) -> Connected { + self.inner.connected().proxy(self.is_proxy) + } +} + +impl Read for Conn { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: ReadBufCursor<'_>, + ) -> Poll> { + let this = self.project(); + Read::poll_read(this.inner, cx, buf) + } +} + +impl Write for Conn { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let this = self.project(); + Write::poll_write(this.inner, cx, buf) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + Write::poll_write_vectored(this.inner, cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + self.inner.is_write_vectored() + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + Write::poll_flush(this.inner, cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + Write::poll_shutdown(this.inner, cx) + } +} + +/// HTTP-only proxy connector for handling HTTP requests through HTTP proxies +/// +/// This connector handles the HTTP proxy logic when no TLS provider is selected, +/// including request URL modification and proxy authentication. +#[derive(Debug, Clone)] +pub(crate) struct HttpProxyConnector { + inner: C, + proxy_config: proxy::ProxyConfig, +} + +impl HttpProxyConnector { + pub(crate) fn new(inner: C, proxy_config: proxy::ProxyConfig) -> Self { + Self { + inner, + proxy_config, + } + } +} + +impl tower::Service for HttpProxyConnector +where + C: tower::Service + Clone + Send + 'static, + C::Response: hyper::rt::Read + + hyper::rt::Write + + hyper_util::client::legacy::connect::Connection + + Send + + Sync + + Unpin + + 'static, + C::Future: Send + 'static, + C::Error: Into, +{ + type Response = connect::Conn; + type Error = BoxError; + type Future = connect::Connecting; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.inner.poll_ready(cx).map_err(Into::into) + } + + fn call(&mut self, dst: Uri) -> Self::Future { + // Check if this request should be proxied + let proxy_intercept = if !self.proxy_config.is_disabled() { + let matcher = self.proxy_config.clone().into_hyper_util_matcher(); + matcher.intercept(&dst) + } else { + None + }; + + if let Some(intercept) = proxy_intercept { + // HTTP through proxy: Connect to proxy server + let proxy_uri = intercept.uri().clone(); + let fut = self.inner.call(proxy_uri); + Box::pin(async move { + let conn = fut.await.map_err(Into::into)?; + Ok(connect::Conn { + inner: Box::new(conn), + is_proxy: true, + }) + }) + } else { + // Direct connection + let fut = self.inner.call(dst); + Box::pin(async move { + let conn = fut.await.map_err(Into::into)?; + Ok(connect::Conn { + inner: Box::new(conn), + is_proxy: false, + }) + }) + } + } +} diff --git a/sources/aws-smithy-http-client/src/client/dns.rs b/sources/aws-smithy-http-client/src/client/dns.rs new file mode 100644 index 000000000..bea83aff2 --- /dev/null +++ b/sources/aws-smithy-http-client/src/client/dns.rs @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +use aws_smithy_runtime_api::client::dns::ResolveDns; +use hyper_util::client::legacy::connect::dns::Name; +use std::error::Error; +use std::future::Future; +use std::net::SocketAddr; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::vec; + +/// A bridge that allows our `ResolveDns` trait to work with Hyper's `Resolver` interface (based on tower) +#[derive(Clone)] +#[allow(dead_code)] +pub(crate) struct HyperUtilResolver { + pub(crate) resolver: R, +} + +impl tower::Service for HyperUtilResolver { + type Response = vec::IntoIter; + type Error = Box; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Name) -> Self::Future { + let resolver = self.resolver.clone(); + Box::pin(async move { + let dns_entries = resolver.resolve_dns(req.as_str()).await?; + Ok(dns_entries + .into_iter() + .map(|ip_addr| SocketAddr::new(ip_addr, 0)) + .collect::>() + .into_iter()) + }) + } +} diff --git a/sources/aws-smithy-http-client/src/client/proxy.rs b/sources/aws-smithy-http-client/src/client/proxy.rs new file mode 100644 index 000000000..a9e521ea0 --- /dev/null +++ b/sources/aws-smithy-http-client/src/client/proxy.rs @@ -0,0 +1,816 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Proxy configuration for HTTP clients +//! +//! This module provides types and utilities for configuring HTTP and HTTPS proxies, +//! including support for environment variable detection, authentication, and bypass rules. + +use http::Uri; +use hyper_util::client::proxy::matcher::Matcher; +use std::fmt; + +/// Proxy configuration for HTTP clients +/// +/// Supports HTTP and HTTPS proxy configuration with authentication and bypass rules. +/// Can be configured programmatically or automatically detected from environment variables. +/// +/// # Examples +/// +/// ```rust +/// use aws_smithy_http_client::proxy::ProxyConfig; +/// +/// // HTTP proxy for all traffic +/// let config = ProxyConfig::http("http://proxy.example.com:8080")?; +/// +/// // HTTPS traffic through HTTP proxy (common case - no TLS needed for proxy connection) +/// let config = ProxyConfig::https("http://proxy.example.com:8080")? +/// .with_basic_auth("username", "password") +/// .no_proxy("localhost,*.internal"); +/// +/// // Detect from environment variables +/// let config = ProxyConfig::from_env(); +/// # Ok::<(), Box>(()) +/// ``` +#[derive(Debug, Clone)] +pub struct ProxyConfig { + inner: ProxyConfigInner, +} + +/// Internal configuration representation +#[derive(Debug, Clone)] +enum ProxyConfigInner { + /// Use environment variable detection + FromEnvironment, + /// Explicit HTTP proxy + Http { + uri: Uri, + auth: Option, + no_proxy: Option, + }, + /// Explicit HTTPS proxy + Https { + uri: Uri, + auth: Option, + no_proxy: Option, + }, + /// Proxy for all traffic + All { + uri: Uri, + auth: Option, + no_proxy: Option, + }, + /// Explicitly disabled + Disabled, +} + +/// Proxy authentication configuration +/// +/// Stored for later conversion to hyper-util format. +#[derive(Debug, Clone)] +struct ProxyAuth { + /// Username for authentication + username: String, + /// Password for authentication + password: String, +} + +/// Errors that can occur during proxy configuration +#[derive(Debug)] +pub struct ProxyError { + kind: ErrorKind, +} + +#[derive(Debug)] +enum ErrorKind { + InvalidUrl(String), +} + +impl From for ProxyError { + fn from(value: ErrorKind) -> Self { + Self { kind: value } + } +} + +impl fmt::Display for ProxyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.kind { + ErrorKind::InvalidUrl(url) => write!(f, "invalid proxy URL: {url}"), + } + } +} + +impl std::error::Error for ProxyError {} + +impl ProxyConfig { + /// Create a new proxy configuration for HTTP traffic only + /// + /// # Arguments + /// * `proxy_url` - The HTTP proxy URL + /// + /// # Examples + /// ```rust + /// use aws_smithy_http_client::proxy::ProxyConfig; + /// + /// let config = ProxyConfig::http("http://proxy.example.com:8080")?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn http(proxy_url: U) -> Result + where + U: TryInto, + U::Error: fmt::Display, + { + let uri = proxy_url + .try_into() + .map_err(|e| ErrorKind::InvalidUrl(e.to_string()))?; + + Self::validate_proxy_uri(&uri)?; + + Ok(ProxyConfig { + inner: ProxyConfigInner::Http { + uri, + auth: None, + no_proxy: None, + }, + }) + } + + /// Create a new proxy configuration for HTTPS traffic only + /// + /// This proxy will only be used for `https://` requests. HTTP requests + /// will connect directly unless a separate HTTP proxy is configured. + /// + /// The proxy URL itself can use either HTTP or HTTPS scheme: + /// - `http://proxy.example.com:8080` - Connect to proxy using HTTP (no TLS needed) + /// - `https://proxy.example.com:8080` - Connect to proxy using HTTPS (TLS required) + /// + /// **Note**: If the proxy URL itself uses HTTPS scheme, TLS support must be + /// available when building the connector, otherwise connections will fail. + /// + /// # Arguments + /// * `proxy_url` - The proxy URL + /// + /// # Examples + /// ```rust + /// use aws_smithy_http_client::proxy::ProxyConfig; + /// + /// // HTTPS traffic through HTTP proxy (no TLS needed for proxy connection) + /// let config = ProxyConfig::https("http://proxy.example.com:8080")?; + /// + /// // HTTPS traffic through HTTPS proxy (TLS needed for proxy connection) + /// let config = ProxyConfig::https("https://secure-proxy.example.com:8080")?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn https(proxy_url: U) -> Result + where + U: TryInto, + U::Error: fmt::Display, + { + let uri = proxy_url + .try_into() + .map_err(|e| ErrorKind::InvalidUrl(e.to_string()))?; + + Self::validate_proxy_uri(&uri)?; + + Ok(ProxyConfig { + inner: ProxyConfigInner::Https { + uri, + auth: None, + no_proxy: None, + }, + }) + } + + /// Create a new proxy configuration for all HTTP and HTTPS traffic + /// + /// This proxy will be used for both `http://` and `https://` requests. + /// This is equivalent to setting both HTTP and HTTPS proxies to the same URL. + /// + /// **Note**: If the proxy URL itself uses HTTPS scheme, TLS support must be + /// available when building the connector, otherwise connections will fail. + /// + /// # Arguments + /// * `proxy_url` - The proxy URL + /// + /// # Examples + /// ```rust + /// use aws_smithy_http_client::proxy::ProxyConfig; + /// + /// let config = ProxyConfig::all("http://proxy.example.com:8080")?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn all(proxy_url: U) -> Result + where + U: TryInto, + U::Error: fmt::Display, + { + let uri = proxy_url + .try_into() + .map_err(|e| ErrorKind::InvalidUrl(e.to_string()))?; + + Self::validate_proxy_uri(&uri)?; + + Ok(ProxyConfig { + inner: ProxyConfigInner::All { + uri, + auth: None, + no_proxy: None, + }, + }) + } + + /// Create a proxy configuration that disables all proxy usage + /// + /// This is useful for explicitly disabling proxy support even when + /// environment variables are set. + /// + /// # Examples + /// ```rust + /// use aws_smithy_http_client::proxy::ProxyConfig; + /// + /// let config = ProxyConfig::disabled(); + /// ``` + pub fn disabled() -> Self { + ProxyConfig { + inner: ProxyConfigInner::Disabled, + } + } + + /// Add basic authentication to this proxy configuration + /// + /// # Arguments + /// * `username` - Username for proxy authentication + /// * `password` - Password for proxy authentication + /// + /// # Examples + /// ```rust + /// use aws_smithy_http_client::proxy::ProxyConfig; + /// + /// let config = ProxyConfig::http("http://proxy.example.com:8080")? + /// .with_basic_auth("username", "password"); + /// # Ok::<(), Box>(()) + /// ``` + pub fn with_basic_auth(mut self, username: U, password: P) -> Self + where + U: Into, + P: Into, + { + let auth = ProxyAuth { + username: username.into(), + password: password.into(), + }; + + match &mut self.inner { + ProxyConfigInner::Http { + auth: ref mut a, .. + } => *a = Some(auth), + ProxyConfigInner::Https { + auth: ref mut a, .. + } => *a = Some(auth), + ProxyConfigInner::All { + auth: ref mut a, .. + } => *a = Some(auth), + ProxyConfigInner::FromEnvironment | ProxyConfigInner::Disabled => { + // Cannot add auth to environment or disabled configs + } + } + + self + } + + /// Add NO_PROXY rules to this configuration + /// + /// NO_PROXY rules specify hosts that should bypass the proxy and connect directly. + /// + /// # Arguments + /// * `rules` - Comma-separated list of bypass rules + /// + /// # Examples + /// ```rust + /// use aws_smithy_http_client::proxy::ProxyConfig; + /// + /// let config = ProxyConfig::http("http://proxy.example.com:8080")? + /// .no_proxy("localhost,127.0.0.1,*.internal,10.0.0.0/8"); + /// # Ok::<(), Box>(()) + /// ``` + pub fn no_proxy>(mut self, rules: S) -> Self { + let rules_str = rules.as_ref().to_string(); + + match &mut self.inner { + ProxyConfigInner::Http { + no_proxy: ref mut n, + .. + } => *n = Some(rules_str), + ProxyConfigInner::Https { + no_proxy: ref mut n, + .. + } => *n = Some(rules_str), + ProxyConfigInner::All { + no_proxy: ref mut n, + .. + } => *n = Some(rules_str), + ProxyConfigInner::FromEnvironment | ProxyConfigInner::Disabled => { + // Cannot add no_proxy to environment or disabled configs + // Environment configs will use NO_PROXY env var + // FIXME - is this what we want? + } + } + + self + } + + /// Create proxy configuration from environment variables + /// + /// Reads standard proxy environment variables: + /// - `HTTP_PROXY` / `http_proxy`: HTTP proxy URL + /// - `HTTPS_PROXY` / `https_proxy`: HTTPS proxy URL + /// - `ALL_PROXY` / `all_proxy`: Proxy for all protocols (fallback) + /// - `NO_PROXY` / `no_proxy`: Comma-separated bypass rules + /// + /// If no proxy environment variables are set, this returns a configuration + /// that won't intercept any requests (equivalent to no proxy). + /// + /// # Examples + /// ```rust + /// use aws_smithy_http_client::proxy::ProxyConfig; + /// + /// // Always succeeds, even if no environment variables are set + /// let config = ProxyConfig::from_env(); + /// ``` + pub fn from_env() -> Self { + // Delegate to environment variable parsing + // If no env vars are set, creates a matcher that doesn't intercept anything + ProxyConfig { + inner: ProxyConfigInner::FromEnvironment, + } + } + + /// Check if proxy is disabled (no proxy configuration) + pub fn is_disabled(&self) -> bool { + matches!(self.inner, ProxyConfigInner::Disabled) + } + + /// Check if this configuration uses environment variables + pub fn is_from_env(&self) -> bool { + matches!(self.inner, ProxyConfigInner::FromEnvironment) + } + + /// Convert this configuration to internal proxy matcher + /// + /// This method converts the user-friendly configuration to the internal + /// proxy matching implementation used by the HTTP client. + pub(crate) fn into_hyper_util_matcher(self) -> Matcher { + match self.inner { + ProxyConfigInner::FromEnvironment => Matcher::from_env(), + ProxyConfigInner::Http { + uri, + auth, + no_proxy, + } => { + let mut builder = Matcher::builder(); + + // Set HTTP proxy with authentication embedded in URL if present + let proxy_url = Self::build_proxy_url(uri, auth); + builder = builder.http(proxy_url); + + // Add NO_PROXY rules if present + if let Some(no_proxy_rules) = no_proxy { + builder = builder.no(no_proxy_rules); + } + + builder.build() + } + ProxyConfigInner::Https { + uri, + auth, + no_proxy, + } => { + let mut builder = Matcher::builder(); + + // Set HTTPS proxy with authentication embedded in URL if present + let proxy_url = Self::build_proxy_url(uri, auth); + builder = builder.https(proxy_url); + + // Add NO_PROXY rules if present + if let Some(no_proxy_rules) = no_proxy { + builder = builder.no(no_proxy_rules); + } + + builder.build() + } + ProxyConfigInner::All { + uri, + auth, + no_proxy, + } => { + let mut builder = Matcher::builder(); + + // Set proxy for all traffic with authentication embedded in URL if present + let proxy_url = Self::build_proxy_url(uri, auth); + builder = builder.all(proxy_url); + + // Add NO_PROXY rules if present + if let Some(no_proxy_rules) = no_proxy { + builder = builder.no(no_proxy_rules); + } + + builder.build() + } + ProxyConfigInner::Disabled => { + // Create an empty matcher that won't intercept anything + Matcher::builder().build() + } + } + } + + /// Check if this proxy configuration requires TLS support + /// + /// Returns true if any of the configured proxy URLs use HTTPS scheme, + /// which requires TLS to establish the connection to the proxy server. + pub(crate) fn requires_tls(&self) -> bool { + match &self.inner { + ProxyConfigInner::Http { uri, .. } => uri.scheme_str() == Some("https"), + ProxyConfigInner::Https { uri, .. } => uri.scheme_str() == Some("https"), + ProxyConfigInner::All { uri, .. } => uri.scheme_str() == Some("https"), + ProxyConfigInner::FromEnvironment => { + // Check environment variables for HTTPS proxy URLs + Self::env_vars_require_tls() + } + ProxyConfigInner::Disabled => false, + } + } + + /// Check if any environment proxy variables contain HTTPS URLs + fn env_vars_require_tls() -> bool { + let proxy_vars = [ + "HTTP_PROXY", + "http_proxy", + "HTTPS_PROXY", + "https_proxy", + "ALL_PROXY", + "all_proxy", + ]; + + for var in &proxy_vars { + if let Ok(proxy_url) = std::env::var(var) { + if !proxy_url.is_empty() { + // Simple check for https:// scheme + if proxy_url.starts_with("https://") { + return true; + } + } + } + } + false + } + + fn validate_proxy_uri(uri: &Uri) -> Result<(), ProxyError> { + // Validate scheme + match uri.scheme_str() { + Some("http") | Some("https") => {} + Some(scheme) => { + return Err( + ErrorKind::InvalidUrl(format!("unsupported proxy scheme: {scheme}")).into(), + ); + } + None => { + return Err(ErrorKind::InvalidUrl( + "proxy URL must include scheme (http:// or https://)".to_string(), + ) + .into()); + } + } + + // Validate host + if uri.host().is_none() { + return Err(ErrorKind::InvalidUrl("proxy URL must include host".to_string()).into()); + } + + Ok(()) + } + + fn build_proxy_url(uri: Uri, auth: Option) -> String { + let uri_str = uri.to_string(); + + if let Some(auth) = auth { + // Embed authentication in the URL: scheme://username:password@host:port/path + if let Some(scheme_end) = uri_str.find("://") { + let scheme = &uri_str[..scheme_end + 3]; + let rest = &uri_str[scheme_end + 3..]; + + // Check if auth is already present in the URI + if rest.contains('@') { + // Auth already present, return as-is + uri_str + } else { + // Add auth to the URI + format!("{}{}:{}@{}", scheme, auth.username, auth.password, rest) + } + } else { + // Invalid URI format, return as-is + uri_str + } + } else { + // No authentication, return URI as-is + uri_str + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + #[test] + fn test_proxy_config_http() { + let config = ProxyConfig::http("http://proxy.example.com:8080").unwrap(); + assert!(!config.is_disabled()); + assert!(!config.is_from_env()); + } + + #[test] + fn test_proxy_config_https() { + let config = ProxyConfig::https("http://proxy.example.com:8080").unwrap(); + assert!(!config.is_disabled()); + assert!(!config.is_from_env()); + } + + #[test] + fn test_proxy_config_all() { + let config = ProxyConfig::all("http://proxy.example.com:8080").unwrap(); + assert!(!config.is_disabled()); + assert!(!config.is_from_env()); + } + + #[test] + fn test_proxy_config_disabled() { + let config = ProxyConfig::disabled(); + assert!(config.is_disabled()); + assert!(!config.is_from_env()); + } + + #[test] + fn test_proxy_config_with_auth() { + let config = ProxyConfig::http("http://proxy.example.com:8080") + .unwrap() + .with_basic_auth("user", "pass"); + + // Auth is stored internally + assert!(!config.is_disabled()); + } + + #[test] + fn test_proxy_config_with_no_proxy() { + let config = ProxyConfig::http("http://proxy.example.com:8080") + .unwrap() + .no_proxy("localhost,*.internal"); + + // NO_PROXY rules are stored internally + assert!(!config.is_disabled()); + } + + #[test] + fn test_proxy_config_invalid_url() { + let result = ProxyConfig::http("not-a-url"); + assert!(result.is_err()); + } + + #[test] + fn test_proxy_config_invalid_scheme() { + let result = ProxyConfig::http("ftp://proxy.example.com:8080"); + assert!(result.is_err()); + } + + #[test] + #[serial_test::serial] + fn test_proxy_config_from_env_with_vars() { + // Save original environment + let original_http = env::var("HTTP_PROXY"); + + // Set test environment + env::set_var("HTTP_PROXY", "http://test-proxy:8080"); + + let config = ProxyConfig::from_env(); + assert!(config.is_from_env()); + + // Restore original environment + match original_http { + Ok(val) => env::set_var("HTTP_PROXY", val), + Err(_) => env::remove_var("HTTP_PROXY"), + } + } + + #[test] + #[serial_test::serial] + fn test_proxy_config_from_env_without_vars() { + // Save original environment + let original_vars: Vec<_> = [ + "HTTP_PROXY", + "http_proxy", + "HTTPS_PROXY", + "https_proxy", + "ALL_PROXY", + "all_proxy", + ] + .iter() + .map(|var| (*var, env::var(var))) + .collect(); + + // Clear all proxy environment variables + for (var, _) in &original_vars { + env::remove_var(var); + } + + let config = ProxyConfig::from_env(); + assert!(config.is_from_env()); + + // Restore original environment + for (var, original_value) in original_vars { + match original_value { + Ok(val) => env::set_var(var, val), + Err(_) => env::remove_var(var), + } + } + } + + #[test] + #[serial_test::serial] + fn test_auth_cannot_be_added_to_env_config() { + // Save original environment + let original_http = env::var("HTTP_PROXY"); + env::set_var("HTTP_PROXY", "http://test-proxy:8080"); + + let config = ProxyConfig::from_env().with_basic_auth("user", "pass"); // This should be ignored + + assert!(config.is_from_env()); + + // Restore original environment + match original_http { + Ok(val) => env::set_var("HTTP_PROXY", val), + Err(_) => env::remove_var("HTTP_PROXY"), + } + } + + #[test] + #[serial_test::serial] + fn test_no_proxy_cannot_be_added_to_env_config() { + // Save original environment + let original_http = env::var("HTTP_PROXY"); + env::set_var("HTTP_PROXY", "http://test-proxy:8080"); + + let config = ProxyConfig::from_env().no_proxy("localhost"); // This should be ignored + + assert!(config.is_from_env()); + + // Restore original environment + match original_http { + Ok(val) => env::set_var("HTTP_PROXY", val), + Err(_) => env::remove_var("HTTP_PROXY"), + } + } + + #[test] + fn test_build_proxy_url_without_auth() { + let uri = "http://proxy.example.com:8080".parse().unwrap(); + let url = ProxyConfig::build_proxy_url(uri, None); + assert_eq!(url, "http://proxy.example.com:8080/"); + } + + #[test] + fn test_build_proxy_url_with_auth() { + let uri = "http://proxy.example.com:8080".parse().unwrap(); + let auth = ProxyAuth { + username: "user".to_string(), + password: "pass".to_string(), + }; + let url = ProxyConfig::build_proxy_url(uri, Some(auth)); + assert_eq!(url, "http://user:pass@proxy.example.com:8080/"); + } + + #[test] + fn test_build_proxy_url_with_existing_auth() { + let uri = "http://existing:creds@proxy.example.com:8080" + .parse() + .unwrap(); + let auth = ProxyAuth { + username: "user".to_string(), + password: "pass".to_string(), + }; + let url = ProxyConfig::build_proxy_url(uri, Some(auth)); + // Should not override existing auth + assert_eq!(url, "http://existing:creds@proxy.example.com:8080/"); + } + + #[test] + #[serial_test::serial] + fn test_into_hyper_util_matcher_from_env() { + // Save original environment + let original_http = env::var("HTTP_PROXY"); + env::set_var("HTTP_PROXY", "http://test-proxy:8080"); + + let config = ProxyConfig::from_env(); + let matcher = config.into_hyper_util_matcher(); + + // Test that the matcher intercepts HTTP requests + let test_uri = "http://example.com".parse().unwrap(); + let intercept = matcher.intercept(&test_uri); + assert!(intercept.is_some()); + + // Restore original environment + match original_http { + Ok(val) => env::set_var("HTTP_PROXY", val), + Err(_) => env::remove_var("HTTP_PROXY"), + } + } + + #[test] + fn test_into_hyper_util_matcher_http() { + let config = ProxyConfig::http("http://proxy.example.com:8080").unwrap(); + let matcher = config.into_hyper_util_matcher(); + + // Test that the matcher intercepts HTTP requests + let test_uri = "http://example.com".parse().unwrap(); + let intercept = matcher.intercept(&test_uri); + assert!(intercept.is_some()); + // The intercept URI might be normalized + assert!(intercept + .unwrap() + .uri() + .to_string() + .starts_with("http://proxy.example.com:8080")); + + // Test that it doesn't intercept HTTPS requests + let https_uri = "https://example.com".parse().unwrap(); + let https_intercept = matcher.intercept(&https_uri); + assert!(https_intercept.is_none()); + } + + #[test] + fn test_into_hyper_util_matcher_with_auth() { + let config = ProxyConfig::http("http://proxy.example.com:8080") + .unwrap() + .with_basic_auth("user", "pass"); + let matcher = config.into_hyper_util_matcher(); + + // Test that the matcher intercepts HTTP requests + let test_uri = "http://example.com".parse().unwrap(); + let intercept = matcher.intercept(&test_uri); + assert!(intercept.is_some()); + + let intercept = intercept.unwrap(); + // The proxy URI should contain the host (auth is handled separately) + assert!(intercept + .uri() + .to_string() + .contains("proxy.example.com:8080")); + + // Test that basic auth is available + assert!(intercept.basic_auth().is_some()); + } + + #[test] + fn test_into_hyper_util_matcher_disabled() { + let config = ProxyConfig::disabled(); + let matcher = config.into_hyper_util_matcher(); + + // Test that the matcher doesn't intercept any requests + let test_uri = "http://example.com".parse().unwrap(); + let intercept = matcher.intercept(&test_uri); + assert!(intercept.is_none()); + } + + #[test] + #[serial_test::serial] + fn test_requires_tls_detection() { + // HTTP proxy should not require TLS + let http_config = ProxyConfig::http("http://proxy.example.com:8080").unwrap(); + assert!(!http_config.requires_tls()); + + // HTTPS proxy URL should require TLS + let https_config = ProxyConfig::http("https://proxy.example.com:8080").unwrap(); + assert!(https_config.requires_tls()); + + // All proxy with HTTP URL should not require TLS + let all_http_config = ProxyConfig::all("http://proxy.example.com:8080").unwrap(); + assert!(!all_http_config.requires_tls()); + + // Environment config with HTTPS proxy should require TLS + env::set_var("HTTP_PROXY", "https://proxy.example.com:8080"); + let env_config = ProxyConfig::from_env(); + assert!(env_config.requires_tls()); // Now detects HTTPS in env vars + env::remove_var("HTTP_PROXY"); + + // Environment config with HTTP proxy should not require TLS + env::set_var("HTTP_PROXY", "http://proxy.example.com:8080"); + let env_config = ProxyConfig::from_env(); + assert!(!env_config.requires_tls()); + env::remove_var("HTTP_PROXY"); + + // Disabled config should not require TLS + let disabled_config = ProxyConfig::disabled(); + assert!(!disabled_config.requires_tls()); + } +} diff --git a/sources/aws-smithy-http-client/src/client/timeout.rs b/sources/aws-smithy-http-client/src/client/timeout.rs new file mode 100644 index 000000000..41659886e --- /dev/null +++ b/sources/aws-smithy-http-client/src/client/timeout.rs @@ -0,0 +1,316 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use std::error::Error; +use std::fmt::Formatter; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Duration; + +use http::Uri; +use pin_project_lite::pin_project; + +use aws_smithy_async::future::timeout::{TimedOutError, Timeout}; +use aws_smithy_async::rt::sleep::Sleep; +use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; +use aws_smithy_runtime_api::box_error::BoxError; + +#[derive(Debug)] +pub(crate) struct HttpTimeoutError { + kind: &'static str, + duration: Duration, +} + +impl std::fmt::Display for HttpTimeoutError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} timeout occurred after {:?}", + self.kind, self.duration + ) + } +} + +impl Error for HttpTimeoutError { + // We implement the `source` function as returning a `TimedOutError` because when `downcast_error` + // or `find_source` is called with an `HttpTimeoutError` (or another error wrapping an `HttpTimeoutError`) + // this method will be checked to determine if it's a timeout-related error. + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&TimedOutError) + } +} + +/// Timeout wrapper that will timeout on the initial TCP connection +/// +/// # Stability +/// This interface is unstable. +#[derive(Clone, Debug)] +pub(crate) struct ConnectTimeout { + inner: I, + timeout: Option<(SharedAsyncSleep, Duration)>, +} + +impl ConnectTimeout { + /// Create a new `ConnectTimeout` around `inner`. + /// + /// Typically, `I` will implement [`hyper_util::client::legacy::connect::Connect`]. + pub(crate) fn new(inner: I, sleep: SharedAsyncSleep, timeout: Duration) -> Self { + Self { + inner, + timeout: Some((sleep, timeout)), + } + } + + pub(crate) fn no_timeout(inner: I) -> Self { + Self { + inner, + timeout: None, + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct HttpReadTimeout { + inner: I, + timeout: Option<(SharedAsyncSleep, Duration)>, +} + +impl HttpReadTimeout { + /// Create a new `HttpReadTimeout` around `inner`. + /// + /// Typically, `I` will implement [`tower::Service>`]. + pub(crate) fn new(inner: I, sleep: SharedAsyncSleep, timeout: Duration) -> Self { + Self { + inner, + timeout: Some((sleep, timeout)), + } + } + + pub(crate) fn no_timeout(inner: I) -> Self { + Self { + inner, + timeout: None, + } + } +} + +pin_project! { + /// Timeout future for Tower services + /// + /// Timeout future to handle timing out, mapping errors, and the possibility of not timing out + /// without incurring an additional allocation for each timeout layer. + #[project = MaybeTimeoutFutureProj] + pub enum MaybeTimeoutFuture { + Timeout { + #[pin] + timeout: Timeout, + error_type: &'static str, + duration: Duration, + }, + NoTimeout { + #[pin] + future: F + } + } +} + +impl Future for MaybeTimeoutFuture +where + F: Future>, + E: Into, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let (timeout_future, kind, &mut duration) = match self.project() { + MaybeTimeoutFutureProj::NoTimeout { future } => { + return future.poll(cx).map_err(|err| err.into()); + } + MaybeTimeoutFutureProj::Timeout { + timeout, + error_type, + duration, + } => (timeout, error_type, duration), + }; + match timeout_future.poll(cx) { + Poll::Ready(Ok(response)) => Poll::Ready(response.map_err(|err| err.into())), + Poll::Ready(Err(_timeout)) => { + Poll::Ready(Err(HttpTimeoutError { kind, duration }.into())) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl tower::Service for ConnectTimeout +where + I: tower::Service, + I::Error: Into, +{ + type Response = I::Response; + type Error = BoxError; + type Future = MaybeTimeoutFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx).map_err(|err| err.into()) + } + + fn call(&mut self, req: Uri) -> Self::Future { + match &self.timeout { + Some((sleep, duration)) => { + let sleep = sleep.sleep(*duration); + MaybeTimeoutFuture::Timeout { + timeout: Timeout::new(self.inner.call(req), sleep), + error_type: "HTTP connect", + duration: *duration, + } + } + None => MaybeTimeoutFuture::NoTimeout { + future: self.inner.call(req), + }, + } + } +} + +impl tower::Service> for HttpReadTimeout +where + I: tower::Service>, + I::Error: Send + Sync + Error + 'static, +{ + type Response = I::Response; + type Error = BoxError; + type Future = MaybeTimeoutFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx).map_err(|err| err.into()) + } + + fn call(&mut self, req: http::Request) -> Self::Future { + match &self.timeout { + Some((sleep, duration)) => { + let sleep = sleep.sleep(*duration); + MaybeTimeoutFuture::Timeout { + timeout: Timeout::new(self.inner.call(req), sleep), + error_type: "HTTP read", + duration: *duration, + } + } + None => MaybeTimeoutFuture::NoTimeout { + future: self.inner.call(req), + }, + } + } +} + +#[cfg(test)] +pub(crate) mod test { + use hyper::rt::ReadBufCursor; + use hyper_util::client::legacy::connect::{Connected, Connection}; + use hyper_util::rt::TokioIo; + use tokio::net::TcpStream; + + use aws_smithy_async::future::never::Never; + + use aws_smithy_runtime_api::box_error::BoxError; + use aws_smithy_runtime_api::client::result::ConnectorError; + use http::Uri; + use hyper::http; + use hyper::rt::{Read, Write}; + use std::future::Future; + use std::pin::Pin; + use std::task::{Context, Poll}; + + #[allow(unused)] + fn connect_timeout_is_correct() { + is_send_sync::>(); + } + + #[allow(unused)] + fn is_send_sync() {} + + /// A service that will never return whatever it is you want + /// + /// Returned futures will return Pending forever + #[non_exhaustive] + #[derive(Clone, Default, Debug)] + pub(crate) struct NeverConnects; + impl tower::Service for NeverConnects { + type Response = TokioIo; + type Error = ConnectorError; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _uri: Uri) -> Self::Future { + Box::pin(async move { + Never::new().await; + unreachable!() + }) + } + } + + /// A service that will connect but never send any data + #[derive(Clone, Debug, Default)] + pub(crate) struct NeverReplies; + impl tower::Service for NeverReplies { + type Response = EmptyStream; + type Error = BoxError; + type Future = std::future::Ready>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _req: Uri) -> Self::Future { + std::future::ready(Ok(EmptyStream)) + } + } + + /// A stream that will never return or accept any data + #[non_exhaustive] + #[derive(Debug, Default)] + pub(crate) struct EmptyStream; + impl Read for EmptyStream { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: ReadBufCursor<'_>, + ) -> Poll> { + Poll::Pending + } + } + impl Write for EmptyStream { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &[u8], + ) -> Poll> { + Poll::Pending + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Pending + } + + fn poll_shutdown( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Pending + } + } + + impl Connection for EmptyStream { + fn connected(&self) -> Connected { + Connected::new() + } + } +} diff --git a/sources/aws-smithy-http-client/src/client/tls.rs b/sources/aws-smithy-http-client/src/client/tls.rs new file mode 100644 index 000000000..187a5228a --- /dev/null +++ b/sources/aws-smithy-http-client/src/client/tls.rs @@ -0,0 +1,141 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +use crate::cfg::cfg_rustls; +use crate::HttpClientError; + +/// Choice of underlying cryptography library +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum Provider { + #[cfg(any( + feature = "rustls-aws-lc", + feature = "rustls-aws-lc-fips", + feature = "rustls-ring" + ))] + /// TLS provider based on [rustls](https://github.com/rustls/rustls) + Rustls(rustls_provider::CryptoMode), +} + +/// TLS related configuration object +#[derive(Debug, Clone)] +pub struct TlsContext { + #[allow(unused)] + trust_store: TrustStore, +} + +impl TlsContext { + /// Create a new [TlsContext] builder + pub fn builder() -> TlsContextBuilder { + TlsContextBuilder::new() + } +} + +impl Default for TlsContext { + fn default() -> Self { + TlsContext::builder().build().expect("valid default config") + } +} + +/// Builder for TLS related configuration +#[derive(Debug)] +pub struct TlsContextBuilder { + trust_store: TrustStore, +} + +impl TlsContextBuilder { + fn new() -> Self { + TlsContextBuilder { + trust_store: TrustStore::default(), + } + } + + /// Configure the trust store to use for the TLS context + pub fn with_trust_store(mut self, trust_store: TrustStore) -> Self { + self.trust_store = trust_store; + self + } + + /// Build a new [TlsContext] + pub fn build(self) -> Result { + Ok(TlsContext { + trust_store: self.trust_store, + }) + } +} + +/// PEM encoded certificate +#[allow(unused)] +#[derive(Debug, Clone)] +struct CertificatePEM(Vec); + +impl From<&[u8]> for CertificatePEM { + fn from(value: &[u8]) -> Self { + CertificatePEM(value.to_vec()) + } +} + +/// Container for root certificates able to provide a root-of-trust for connection authentication +/// +/// Platform native root certificates are enabled by default. To start with a clean trust +/// store use [TrustStore::empty] +#[derive(Debug, Clone)] +pub struct TrustStore { + enable_native_roots: bool, + custom_certs: Vec, +} + +impl TrustStore { + /// Create a new empty trust store + pub fn empty() -> Self { + Self { + enable_native_roots: false, + custom_certs: Vec::new(), + } + } + + /// Enable or disable using the platform's native trusted root certificate store + /// + /// Default: true + pub fn with_native_roots(mut self, enable_native_roots: bool) -> Self { + self.enable_native_roots = enable_native_roots; + self + } + + /// Add the PEM encoded certificate to the trust store + /// + /// This may be called more than once to add multiple certificates. + /// NOTE: PEM certificate contents are not validated until passed to the configured + /// TLS provider. + pub fn with_pem_certificate(mut self, pem_bytes: impl Into>) -> Self { + // ideally we'd validate here but rustls-pki-types converts to DER when loading and S2N + // still expects PEM encoding. Store the raw bytes and let the TLS implementation validate + self.custom_certs.push(CertificatePEM(pem_bytes.into())); + self + } + + /// Add the PEM encoded certificate to the trust store + /// + /// This may be called more than once to add multiple certificates. + /// NOTE: PEM certificate contents are not validated until passed to the configured + /// TLS provider. + pub fn add_pem_certificate(&mut self, pem_bytes: impl Into>) -> &mut Self { + self.custom_certs.push(CertificatePEM(pem_bytes.into())); + self + } +} + +impl Default for TrustStore { + fn default() -> Self { + Self { + enable_native_roots: true, + custom_certs: Vec::new(), + } + } +} + +cfg_rustls! { + /// rustls based support and adapters + pub mod rustls_provider; +} diff --git a/sources/aws-smithy-http-client/src/client/tls/rustls_provider.rs b/sources/aws-smithy-http-client/src/client/tls/rustls_provider.rs new file mode 100644 index 000000000..b841ba0d5 --- /dev/null +++ b/sources/aws-smithy-http-client/src/client/tls/rustls_provider.rs @@ -0,0 +1,441 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +use crate::client::tls::Provider; +use rustls::crypto::CryptoProvider; + +/// Choice of underlying cryptography library (this only applies to rustls) +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum CryptoMode { + /// Crypto based on [ring](https://github.com/briansmith/ring) + #[cfg(feature = "rustls-ring")] + Ring, + /// Crypto based on [aws-lc](https://github.com/aws/aws-lc-rs) + #[cfg(feature = "rustls-aws-lc")] + AwsLc, + /// FIPS compliant variant of [aws-lc](https://github.com/aws/aws-lc-rs) + #[cfg(feature = "rustls-aws-lc-fips")] + AwsLcFips, + /// Custom provider — allows caller to supply their own rustls CryptoProvider + Custom(CryptoProvider), +} + +impl CryptoMode { + fn provider(self) -> CryptoProvider { + match self { + #[cfg(feature = "rustls-aws-lc")] + CryptoMode::AwsLc => rustls::crypto::aws_lc_rs::default_provider(), + + #[cfg(feature = "rustls-ring")] + CryptoMode::Ring => rustls::crypto::ring::default_provider(), + + #[cfg(feature = "rustls-aws-lc-fips")] + CryptoMode::AwsLcFips => { + let provider = rustls::crypto::default_fips_provider(); + assert!( + provider.fips(), + "FIPS was requested but the provider did not support FIPS" + ); + provider + } + + CryptoMode::Custom(provider) => provider, + } + } +} + +impl Provider { + /// Create a TLS provider based on [rustls](https://github.com/rustls/rustls) + /// and the given [`CryptoMode`] + pub fn rustls(mode: CryptoMode) -> Provider { + Provider::Rustls(mode) + } +} + +pub(crate) mod build_connector { + use crate::client::tls::rustls_provider::CryptoMode; + use crate::tls::TlsContext; + use client::connect::HttpConnector; + use hyper_util::client::legacy as client; + use rustls::crypto::CryptoProvider; + use rustls_native_certs::CertificateResult; + use rustls_pki_types::pem::PemObject; + use rustls_pki_types::CertificateDer; + use std::sync::Arc; + use std::sync::LazyLock; + + /// Cached native certificates + /// + /// Creating a `with_native_roots()` hyper_rustls client re-loads system certs + /// each invocation (which can take 300ms on OSx). Cache the loaded certs + /// to avoid repeatedly incurring that cost. + pub(crate) static NATIVE_ROOTS: LazyLock>> = LazyLock::new(|| { + let CertificateResult { certs, errors, .. } = rustls_native_certs::load_native_certs(); + if !errors.is_empty() { + tracing::warn!("native root CA certificate loading errors: {errors:?}") + } + + if certs.is_empty() { + tracing::warn!("no native root CA certificates found!"); + } + + // NOTE: unlike hyper-rustls::with_native_roots we don't validate here, we'll do that later + // for now we have a collection of certs that may or may not be valid. + certs + }); + + pub(crate) fn restrict_ciphers(base: CryptoProvider) -> CryptoProvider { + let suites = &[ + rustls::CipherSuite::TLS13_AES_256_GCM_SHA384, + rustls::CipherSuite::TLS13_AES_128_GCM_SHA256, + // TLS1.2 suites + rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + rustls::CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ]; + let supported_suites = suites + .iter() + .flat_map(|suite| { + base.cipher_suites + .iter() + .find(|s| &s.suite() == suite) + .cloned() + }) + .collect::>(); + CryptoProvider { + cipher_suites: supported_suites, + ..base + } + } + + impl TlsContext { + pub(crate) fn rustls_root_certs(&self) -> rustls::RootCertStore { + let mut roots = rustls::RootCertStore::empty(); + if self.trust_store.enable_native_roots { + let (valid, _invalid) = roots.add_parsable_certificates(NATIVE_ROOTS.clone()); + debug_assert!(valid > 0, "TrustStore configured to enable native roots but no valid root certificates parsed!"); + } + + for pem_cert in &self.trust_store.custom_certs { + let ders = CertificateDer::pem_slice_iter(&pem_cert.0) + .collect::, _>>() + .expect("valid PEM certificate"); + for cert in ders { + roots.add(cert).expect("cert parsable") + } + } + + roots + } + } + + /// Create a rustls ClientConfig with smithy-rs defaults + /// + /// This centralizes the rustls ClientConfig creation logic to ensure + /// consistency between the main HTTPS connector and tunnel handlers. + pub(crate) fn create_rustls_client_config( + crypto_mode: CryptoMode, + tls_context: &TlsContext, + ) -> rustls::ClientConfig { + let root_certs = tls_context.rustls_root_certs(); + rustls::ClientConfig::builder_with_provider(Arc::new(restrict_ciphers(crypto_mode.provider()))) + .with_safe_default_protocol_versions() + .expect("Error with the TLS configuration. Please file a bug report under https://github.com/smithy-lang/smithy-rs/issues.") + .with_root_certificates(root_certs) + .with_no_client_auth() + } + + pub(crate) fn wrap_connector( + mut conn: HttpConnector, + crypto_mode: CryptoMode, + tls_context: &TlsContext, + proxy_config: crate::client::proxy::ProxyConfig, + ) -> super::connect::RustTlsConnector { + let client_config = create_rustls_client_config(crypto_mode, tls_context); + conn.enforce_http(false); + let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(client_config.clone()) + .https_or_http() + .enable_http1() + .enable_http2() + .wrap_connector(conn); + + super::connect::RustTlsConnector::new(https_connector, client_config, proxy_config) + } +} + +pub(crate) mod connect { + use crate::client::connect::{Conn, Connecting}; + use crate::client::proxy::ProxyConfig; + use aws_smithy_runtime_api::box_error::BoxError; + use http::uri::Scheme; + use http::Uri; + use hyper::rt::{Read, ReadBufCursor, Write}; + use hyper_rustls::MaybeHttpsStream; + use hyper_util::client::legacy::connect::{Connected, Connection, HttpConnector}; + use hyper_util::client::proxy::matcher::Matcher; + use hyper_util::rt::TokioIo; + use pin_project_lite::pin_project; + use std::error::Error; + use std::sync::Arc; + use std::{ + io::{self, IoSlice}, + pin::Pin, + task::{Context, Poll}, + }; + use tokio::io::{AsyncRead, AsyncWrite}; + use tokio::net::TcpStream; + use tokio_rustls::client::TlsStream; + use tower::Service; + + #[derive(Debug, Clone)] + pub(crate) struct RustTlsConnector { + https: hyper_rustls::HttpsConnector>, + tls_config: Arc, + proxy_matcher: Option>, // Pre-computed for performance + } + + impl RustTlsConnector { + pub(super) fn new( + https: hyper_rustls::HttpsConnector>, + tls_config: rustls::ClientConfig, + proxy_config: ProxyConfig, + ) -> Self { + // Pre-compute the proxy matcher once during construction + let proxy_matcher = if proxy_config.is_disabled() { + None + } else { + Some(Arc::new(proxy_config.into_hyper_util_matcher())) + }; + + Self { + https, + tls_config: Arc::new(tls_config), + proxy_matcher, + } + } + } + + impl Service for RustTlsConnector + where + R: Clone + Send + Sync + 'static, + R: Service, + R::Response: Iterator, + R::Future: Send, + R::Error: Into>, + { + type Response = Conn; + type Error = BoxError; + type Future = Connecting; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.https.poll_ready(cx).map_err(Into::into) + } + + fn call(&mut self, dst: Uri) -> Self::Future { + // Check if this request should be proxied using pre-computed matcher + let proxy_intercept = if let Some(ref matcher) = self.proxy_matcher { + matcher.intercept(&dst) + } else { + None + }; + + if let Some(intercept) = proxy_intercept { + if dst.scheme() == Some(&Scheme::HTTPS) { + // HTTPS through HTTP proxy: Use CONNECT tunneling + manual TLS + self.handle_https_through_proxy(dst, intercept) + } else { + // HTTP through proxy: Direct connection to proxy + self.handle_http_through_proxy(dst, intercept) + } + } else { + // Direct connection: Use the existing HTTPS connector + self.handle_direct_connection(dst) + } + } + } + + impl RustTlsConnector + where + R: Clone + Send + Sync + 'static, + R: Service, + R::Response: Iterator, + R::Future: Send, + R::Error: Into>, + { + fn handle_direct_connection(&mut self, dst: Uri) -> Connecting { + let fut = self.https.call(dst); + Box::pin(async move { + let conn = fut.await?; + Ok(Conn { + inner: Box::new(conn), + is_proxy: false, + }) + }) + } + + fn handle_http_through_proxy( + &mut self, + _dst: Uri, + intercept: hyper_util::client::proxy::matcher::Intercept, + ) -> Connecting { + // For HTTP through proxy, connect to the proxy and let it handle the request + let proxy_uri = intercept.uri().clone(); + let fut = self.https.call(proxy_uri); + Box::pin(async move { + let conn = fut.await?; + Ok(Conn { + inner: Box::new(conn), + is_proxy: true, + }) + }) + } + + fn handle_https_through_proxy( + &mut self, + dst: Uri, + intercept: hyper_util::client::proxy::matcher::Intercept, + ) -> Connecting { + use rustls_pki_types::ServerName; + // For HTTPS through HTTP proxy, we need to: + // 1. Establish CONNECT tunnel using the HTTPS connector + // 2. Perform manual TLS handshake over the tunneled stream + + let tunnel = hyper_util::client::legacy::connect::proxy::Tunnel::new( + intercept.uri().clone(), + self.https.clone(), + ); + + // Configure tunnel with authentication if present + let mut tunnel = if let Some(auth) = intercept.basic_auth() { + tunnel.with_auth(auth.clone()) + } else { + tunnel + }; + + let tls_config = self.tls_config.clone(); + let dst_clone = dst.clone(); + + Box::pin(async move { + // Establish CONNECT tunnel + tracing::trace!("tunneling HTTPS over proxy"); + let tunneled = tunnel + .call(dst_clone.clone()) + .await + .map_err(|e| BoxError::from(format!("CONNECT tunnel failed: {e}")))?; + + // Stage 2: Manual TLS handshake over tunneled stream + let host = dst_clone + .host() + .ok_or("missing host in URI for TLS handshake")?; + + let server_name = ServerName::try_from(host.to_owned()).map_err(|e| { + BoxError::from(format!("invalid server name for TLS handshake: {e}")) + })?; + + let tls_connector = tokio_rustls::TlsConnector::from(tls_config) + .connect(server_name, TokioIo::new(tunneled)) + .await?; + + Ok(Conn { + inner: Box::new(RustTlsConn { + inner: TokioIo::new(tls_connector), + }), + is_proxy: true, + }) + }) + } + } + + pin_project! { + pub(crate) struct RustTlsConn { + #[pin] pub(super) inner: TokioIo> + } + } + + impl Connection for RustTlsConn>> { + fn connected(&self) -> Connected { + if self.inner.inner().get_ref().1.alpn_protocol() == Some(b"h2") { + self.inner + .inner() + .get_ref() + .0 + .inner() + .connected() + .negotiated_h2() + } else { + self.inner.inner().get_ref().0.inner().connected() + } + } + } + + impl Connection for RustTlsConn>>> { + fn connected(&self) -> Connected { + if self.inner.inner().get_ref().1.alpn_protocol() == Some(b"h2") { + self.inner + .inner() + .get_ref() + .0 + .inner() + .connected() + .negotiated_h2() + } else { + self.inner.inner().get_ref().0.inner().connected() + } + } + } + impl Read for RustTlsConn { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: ReadBufCursor<'_>, + ) -> Poll> { + let this = self.project(); + Read::poll_read(this.inner, cx, buf) + } + } + + impl Write for RustTlsConn { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let this = self.project(); + Write::poll_write(this.inner, cx, buf) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + Write::poll_write_vectored(this.inner, cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + self.inner.is_write_vectored() + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let this = self.project(); + Write::poll_flush(this.inner, cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let this = self.project(); + Write::poll_shutdown(this.inner, cx) + } + } +} diff --git a/sources/aws-smithy-http-client/src/error.rs b/sources/aws-smithy-http-client/src/error.rs new file mode 100644 index 000000000..e8e04a549 --- /dev/null +++ b/sources/aws-smithy-http-client/src/error.rs @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +use aws_smithy_runtime_api::box_error::BoxError; +use std::fmt; + +/// HTTP client errors +/// +/// This is normally due to configuration issues, internal SDK bugs, or other user error. +#[derive(Debug)] +pub struct HttpClientError { + source: Option, +} + +impl fmt::Display for HttpClientError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "unknown HTTP client error") + } +} + +impl std::error::Error for HttpClientError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.source.as_ref().map(|err| err.as_ref() as _) + } +} diff --git a/sources/aws-smithy-http-client/src/lib.rs b/sources/aws-smithy-http-client/src/lib.rs new file mode 100644 index 000000000..8e1c21b7d --- /dev/null +++ b/sources/aws-smithy-http-client/src/lib.rs @@ -0,0 +1,72 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#![cfg_attr(docsrs, feature(doc_cfg))] + +//! HTTP client implementation for smithy-rs generated code. +//! +//! # Crate Features +//! +//! - `default-client`: Enable default HTTP client implementation (based on hyper 1.x). +//! - `rustls-ring`: Enable TLS provider based on `rustls` using `ring` as the crypto provider +//! - `rustls-aws-lc`: Enable TLS provider based on `rustls` using `aws-lc` as the crypto provider +//! - `rustls-aws-lc-fips`: Same as `rustls-aws-lc` feature but using a FIPS compliant version of `aws-lc` + +#![warn( + missing_docs, + rustdoc::missing_crate_level_docs, + unreachable_pub, + rust_2018_idioms +)] + +/// Default HTTP and TLS connectors +#[cfg(feature = "default-client")] +pub(crate) mod client; +#[cfg(feature = "default-client")] +pub use client::{default_connector, proxy, tls, Builder, Connector, ConnectorBuilder}; + +mod error; +pub use error::HttpClientError; + +#[allow(unused_macros, unused_imports)] +#[macro_use] +pub(crate) mod cfg { + /// Any TLS provider enabled + macro_rules! cfg_tls { + ($($item:item)*) => { + $( + #[cfg(any( + feature = "rustls-aws-lc", + feature = "rustls-aws-lc-fips", + feature = "rustls-ring", + ))] + #[cfg_attr(docsrs, doc(cfg(any( + feature = "rustls-aws-lc", + feature = "rustls-aws-lc-fips", + feature = "rustls-ring", + ))))] + $item + )* + } + } + + /// Any rustls provider enabled + macro_rules! cfg_rustls { + ($($item:item)*) => { + $( + #[cfg(any( + feature = "rustls-aws-lc", + feature = "rustls-aws-lc-fips", + feature = "rustls-ring" + ))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "rustls-aws-lc", feature = "rustls-aws-lc-fips", feature = "rustls-ring"))))] + $item + )* + } + } + + pub(crate) use cfg_rustls; + pub(crate) use cfg_tls; +} From 73ddc66cb5a7b472108a87d90e7fdcf328d60a9a Mon Sep 17 00:00:00 2001 From: Jingwei Wang Date: Tue, 2 Jun 2026 18:15:35 +0000 Subject: [PATCH 4/8] pluto: use aws-smithy-http-client for TLS configuration Replace the deprecated aws-smithy-experimental crate with aws-smithy-http-client for HTTP client construction in pluto. The new crate provides explicit TLS provider selection and proxy configuration through the ConnectorBuilder API. Signed-off-by: Jingwei Wang --- sources/Cargo.lock | 2 +- sources/api/pluto/Cargo.toml | 4 ++-- sources/api/pluto/src/ec2.rs | 26 ++++++++++++++++++++------ sources/api/pluto/src/eks.rs | 26 ++++++++++++++++++++------ sources/api/pluto/src/main.rs | 2 +- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/sources/Cargo.lock b/sources/Cargo.lock index d73c3943b..b33c087b1 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -4582,7 +4582,7 @@ dependencies = [ "aws-lc-rs", "aws-sdk-ec2", "aws-sdk-eks", - "aws-smithy-experimental", + "aws-smithy-http-client 0.1.0", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", diff --git a/sources/api/pluto/Cargo.toml b/sources/api/pluto/Cargo.toml index 614700dd6..ef2919e17 100644 --- a/sources/api/pluto/Cargo.toml +++ b/sources/api/pluto/Cargo.toml @@ -10,7 +10,7 @@ build = "build.rs" exclude = ["README.md"] [features] -fips = ["aws-lc-rs/fips", "aws-smithy-experimental/crypto-aws-lc-fips", "rustls/fips"] +fips = ["aws-lc-rs/fips", "aws-smithy-http-client/rustls-aws-lc-fips"] [dependencies] argh.workspace = true @@ -18,7 +18,7 @@ aws-config.workspace = true aws-lc-rs = { workspace = true, features = ["bindgen"] } aws-sdk-eks.workspace = true aws-sdk-ec2.workspace = true -aws-smithy-experimental = {workspace = true, features = ["crypto-aws-lc"]} +aws-smithy-http-client = { workspace = true, features = ["rustls-aws-lc"] } aws-smithy-runtime-api.workspace = true aws-smithy-types.workspace = true aws-types.workspace = true diff --git a/sources/api/pluto/src/ec2.rs b/sources/api/pluto/src/ec2.rs index ba32ae3e5..93dce35f0 100644 --- a/sources/api/pluto/src/ec2.rs +++ b/sources/api/pluto/src/ec2.rs @@ -1,6 +1,6 @@ use crate::aws::sdk_config; use crate::PROVIDER; -use aws_smithy_experimental::hyper_1_0::HyperClientBuilder; +use aws_smithy_http_client::{proxy::ProxyConfig, tls, Builder as HttpClientBuilder, Connector}; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::error::metadata::ProvideErrorMetadata; use snafu::{OptionExt, ResultExt, Snafu}; @@ -37,6 +37,11 @@ pub(super) enum Error { #[snafu(display("Missing field '{}' in EC2 response", field))] Missing { field: &'static str }, + + #[snafu(display("Invalid proxy URL: {}", source))] + ProxyConfig { + source: aws_smithy_http_client::proxy::ProxyError, + }, } type Result = std::result::Result; @@ -113,12 +118,21 @@ where { let http_client = if let Some(https_proxy) = https_proxy { let https_proxy = https_proxy.as_ref().to_string(); - HyperClientBuilder::new() - .crypto_mode(PROVIDER) - .build_with_proxy(https_proxy, no_proxy) + let mut proxy = ProxyConfig::https(&https_proxy).context(ProxyConfigSnafu)?; + if let Some(no_proxy) = no_proxy { + let no_proxy_str: Vec<&str> = no_proxy.iter().map(|s| s.as_ref()).collect(); + proxy = proxy.no_proxy(no_proxy_str.join(",")); + } + HttpClientBuilder::new().build_with_connector_fn(move |settings, _runtime_components| { + let mut builder = Connector::builder() + .proxy_config(proxy.clone()) + .tls_provider(tls::Provider::Rustls(PROVIDER.clone())); + builder.set_connector_settings(settings.cloned()); + builder.build() + }) } else { - HyperClientBuilder::new() - .crypto_mode(PROVIDER) + HttpClientBuilder::new() + .tls_provider(tls::Provider::Rustls(PROVIDER.clone())) .build_https() }; let ec2_config = aws_sdk_ec2::config::Builder::from(&config) diff --git a/sources/api/pluto/src/eks.rs b/sources/api/pluto/src/eks.rs index dc36f17a5..a36c337f0 100644 --- a/sources/api/pluto/src/eks.rs +++ b/sources/api/pluto/src/eks.rs @@ -1,7 +1,7 @@ use crate::aws::sdk_config; use crate::PROVIDER; use aws_sdk_eks::types::KubernetesNetworkConfigResponse; -use aws_smithy_experimental::hyper_1_0::HyperClientBuilder; +use aws_smithy_http_client::{proxy::ProxyConfig, tls, Builder as HttpClientBuilder, Connector}; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::error::metadata::ProvideErrorMetadata; use snafu::{OptionExt, ResultExt, Snafu}; @@ -34,6 +34,11 @@ pub(super) enum Error { #[snafu(display("Missing field '{}' in EKS response", field))] Missing { field: &'static str }, + + #[snafu(display("Invalid proxy URL: {}", source))] + ProxyConfig { + source: aws_smithy_http_client::proxy::ProxyError, + }, } type Result = std::result::Result; @@ -95,12 +100,21 @@ where { let http_client = if let Some(https_proxy) = https_proxy { let https_proxy = https_proxy.as_ref().to_string(); - HyperClientBuilder::new() - .crypto_mode(PROVIDER) - .build_with_proxy(https_proxy, no_proxy) + let mut proxy = ProxyConfig::https(&https_proxy).context(ProxyConfigSnafu)?; + if let Some(no_proxy) = no_proxy { + let no_proxy_str: Vec<&str> = no_proxy.iter().map(|s| s.as_ref()).collect(); + proxy = proxy.no_proxy(no_proxy_str.join(",")); + } + HttpClientBuilder::new().build_with_connector_fn(move |settings, _runtime_components| { + let mut builder = Connector::builder() + .proxy_config(proxy.clone()) + .tls_provider(tls::Provider::Rustls(PROVIDER.clone())); + builder.set_connector_settings(settings.cloned()); + builder.build() + }) } else { - HyperClientBuilder::new() - .crypto_mode(PROVIDER) + HttpClientBuilder::new() + .tls_provider(tls::Provider::Rustls(PROVIDER.clone())) .build_https() }; let eks_config = aws_sdk_eks::config::Builder::from(&config) diff --git a/sources/api/pluto/src/main.rs b/sources/api/pluto/src/main.rs index 92e986c09..e97186a91 100644 --- a/sources/api/pluto/src/main.rs +++ b/sources/api/pluto/src/main.rs @@ -29,7 +29,7 @@ mod eks; use api::{settings_view_get, settings_view_set, SettingsViewDelta}; use argh::FromArgs; use aws_sdk_eks::types::IpFamily; -use aws_smithy_experimental::hyper_1_0::CryptoMode; +use aws_smithy_http_client::tls::rustls_provider::CryptoMode; use base64::Engine; use bottlerocket_modeled_types::{KubernetesClusterDnsIp, KubernetesHostnameOverrideSource}; use imdsclient::ImdsClient; From 96db045e96a14a8a42a67413a7e672b0e9c15060 Mon Sep 17 00:00:00 2001 From: Jingwei Wang Date: Tue, 2 Jun 2026 18:23:27 +0000 Subject: [PATCH 5/8] cfsignal: switch to aws-smithy-http-client for HTTP transport Replace aws-smithy-experimental with aws-smithy-http-client for CloudFormation signal HTTP client construction. The new crate provides explicit TLS provider selection and proxy configuration through the ConnectorBuilder API. Signed-off-by: Jingwei Wang --- sources/Cargo.lock | 2 +- sources/cfsignal/Cargo.toml | 4 ++-- sources/cfsignal/src/cloudformation.rs | 23 +++++++++++++++-------- sources/cfsignal/src/error.rs | 5 +++++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/sources/Cargo.lock b/sources/Cargo.lock index b33c087b1..3992c41f1 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -1993,7 +1993,7 @@ dependencies = [ "aws-config", "aws-lc-rs", "aws-sdk-cloudformation", - "aws-smithy-experimental", + "aws-smithy-http-client 0.1.0", "aws-types", "generate-readme", "imdsclient", diff --git a/sources/cfsignal/Cargo.toml b/sources/cfsignal/Cargo.toml index 52cb8b496..8e5747a20 100644 --- a/sources/cfsignal/Cargo.toml +++ b/sources/cfsignal/Cargo.toml @@ -8,13 +8,13 @@ publish = false exclude = ["README.md"] [features] -fips = ["rustls/fips", "aws-lc-rs/fips", "aws-smithy-experimental/crypto-aws-lc-fips"] +fips = ["aws-lc-rs/fips", "aws-smithy-http-client/rustls-aws-lc-fips"] [dependencies] aws-config.workspace = true aws-lc-rs = { workspace = true, features = ["bindgen"] } aws-sdk-cloudformation.workspace = true -aws-smithy-experimental = { workspace = true, features = ["crypto-aws-lc"] } +aws-smithy-http-client = { workspace = true, features = ["rustls-aws-lc"] } aws-types.workspace = true imdsclient.workspace = true log.workspace = true diff --git a/sources/cfsignal/src/cloudformation.rs b/sources/cfsignal/src/cloudformation.rs index b5fc88556..484fd3a85 100644 --- a/sources/cfsignal/src/cloudformation.rs +++ b/sources/cfsignal/src/cloudformation.rs @@ -1,7 +1,7 @@ use crate::error::{self, Result}; - use aws_config::BehaviorVersion; -use aws_smithy_experimental::hyper_1_0::{CryptoMode, HyperClientBuilder}; +use aws_smithy_http_client::tls::rustls_provider::CryptoMode; +use aws_smithy_http_client::{proxy::ProxyConfig, tls, Builder as HttpClientBuilder, Connector}; use aws_types::region::Region; use imdsclient::ImdsClient; use log::info; @@ -45,13 +45,20 @@ pub async fn signal_resource( }; let http_client = if let Some(https_proxy) = https_proxy { - let no_proxy = no_proxy.as_deref(); - HyperClientBuilder::new() - .crypto_mode(crypto_mode) - .build_with_proxy(https_proxy, no_proxy) + let mut proxy = ProxyConfig::https(&https_proxy).context(error::ProxyConfigSnafu)?; + if let Some(ref no_proxy) = no_proxy { + proxy = proxy.no_proxy(no_proxy.join(",")); + } + HttpClientBuilder::new().build_with_connector_fn(move |settings, _runtime_components| { + let mut builder = Connector::builder() + .proxy_config(proxy.clone()) + .tls_provider(tls::Provider::Rustls(crypto_mode.clone())); + builder.set_connector_settings(settings.cloned()); + builder.build() + }) } else { - HyperClientBuilder::new() - .crypto_mode(crypto_mode) + HttpClientBuilder::new() + .tls_provider(tls::Provider::Rustls(crypto_mode)) .build_https() }; diff --git a/sources/cfsignal/src/error.rs b/sources/cfsignal/src/error.rs index a959a1de6..99109c262 100644 --- a/sources/cfsignal/src/error.rs +++ b/sources/cfsignal/src/error.rs @@ -42,4 +42,9 @@ pub enum Error { >, >, }, + + #[snafu(display("Invalid proxy URL: {}", source))] + ProxyConfig { + source: aws_smithy_http_client::proxy::ProxyError, + }, } From f62b6230d8097895a0127ee374f867dbcdcfb9a4 Mon Sep 17 00:00:00 2001 From: Jingwei Wang Date: Tue, 2 Jun 2026 20:36:11 +0000 Subject: [PATCH 6/8] sources: remove aws-smithy-experimental crate Remove the deprecated aws-smithy-experimental crate from the workspace. This crate has been superseded by aws-smithy-http-client which provides the same HTTP client functionality with support for custom CryptoProvider injection. No binaries depend on aws-smithy-experimental after the pluto and cfsignal move to aws-smithy-http-client. Signed-off-by: Jingwei Wang --- sources/Cargo.lock | 409 +---- sources/Cargo.toml | 2 - sources/aws-smithy-experimental/Cargo.toml | 41 - sources/aws-smithy-experimental/LICENSE | 175 --- sources/aws-smithy-experimental/README.md | 14 - .../aws-smithy-experimental/src/hyper_1_0.rs | 1322 ----------------- sources/aws-smithy-experimental/src/lib.rs | 10 - sources/aws-smithy-http-client/Cargo.toml | 3 + sources/deny.toml | 15 +- 9 files changed, 51 insertions(+), 1940 deletions(-) delete mode 100644 sources/aws-smithy-experimental/Cargo.toml delete mode 100644 sources/aws-smithy-experimental/LICENSE delete mode 100644 sources/aws-smithy-experimental/README.md delete mode 100644 sources/aws-smithy-experimental/src/hyper_1_0.rs delete mode 100644 sources/aws-smithy-experimental/src/lib.rs diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 3992c41f1..1cc868480 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -563,16 +563,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "assert-json-diff" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "async-broadcast" version = "0.7.2" @@ -1049,30 +1039,6 @@ dependencies = [ "crc32fast", ] -[[package]] -name = "aws-smithy-experimental" -version = "0.1.4" -dependencies = [ - "aws-smithy-async", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "h2 0.4.15", - "headers", - "http 1.4.2", - "hyper 1.10.1", - "hyper-http-proxy", - "hyper-rustls 0.27.9", - "hyper-util", - "once_cell", - "pin-project-lite", - "rustls 0.23.42", - "tokio", - "tower 0.4.13", - "tracing", - "url", -] - [[package]] name = "aws-smithy-http" version = "0.64.0" @@ -1109,11 +1075,12 @@ dependencies = [ "hyper-util", "pin-project-lite", "rustls 0.23.42", - "rustls-native-certs 0.8.4", + "rustls-native-certs", "rustls-pki-types", + "serial_test", "tokio", "tokio-rustls 0.26.4", - "tower 0.5.3", + "tower", "tracing", "url", ] @@ -1125,32 +1092,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "635d23afda0a6ab48d666c4d447c4873e8d1e83518a2be2093122397e50b838e" dependencies = [ "aws-smithy-async", - "aws-smithy-protocol-test", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes", "h2 0.3.27", "h2 0.4.15", "http 0.2.12", "http 1.4.2", "http-body 0.4.6", - "http-body 1.1.0", "hyper 0.14.32", "hyper 1.10.1", "hyper-rustls 0.24.2", "hyper-rustls 0.27.9", "hyper-util", - "indexmap", "pin-project-lite", "rustls 0.21.12", "rustls 0.23.42", - "rustls-native-certs 0.8.4", + "rustls-native-certs", "rustls-pki-types", - "serde", - "serde_json", "tokio", "tokio-rustls 0.26.4", - "tower 0.5.3", + "tower", "tracing", ] @@ -1174,25 +1135,6 @@ dependencies = [ "aws-smithy-runtime-api", ] -[[package]] -name = "aws-smithy-protocol-test" -version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76511a0e223ce78deb6a78b8afebda99cb737cfbc8a58d96dcb190f012dd40a" -dependencies = [ - "assert-json-diff", - "aws-smithy-runtime-api", - "base64-simd", - "cbor-diag", - "ciborium", - "http 0.2.12", - "pretty_assertions", - "regex-lite", - "roxmltree", - "serde_json", - "thiserror 2.0.18", -] - [[package]] name = "aws-smithy-query" version = "0.61.1" @@ -1227,7 +1169,6 @@ dependencies = [ "pin-utils", "tokio", "tracing", - "tracing-subscriber", ] [[package]] @@ -1766,15 +1707,6 @@ dependencies = [ "which", ] -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - [[package]] name = "bstr" version = "0.2.17" @@ -1916,25 +1848,6 @@ dependencies = [ "toml 1.1.3+spec-1.1.0", ] -[[package]] -name = "cbor-diag" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc245b6ecd09b23901a4fbad1ad975701fd5061ceaef6afa93a2d70605a64429" -dependencies = [ - "bs58", - "chrono", - "data-encoding", - "half", - "nom", - "num-bigint", - "num-rational", - "num-traits", - "separator", - "url", - "uuid", -] - [[package]] name = "cc" version = "1.2.67" @@ -2029,33 +1942,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - [[package]] name = "cidr" version = "0.2.3" @@ -2180,16 +2066,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -2306,12 +2182,6 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - [[package]] name = "crypto-bigint" version = "0.5.5" @@ -2505,12 +2375,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.10.7" @@ -3163,17 +3027,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - [[package]] name = "handlebars" version = "4.5.0" @@ -3225,30 +3078,6 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64", - "bytes", - "headers-core", - "http 1.4.2", - "httpdate", - "mime", - "sha1 0.10.7", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http 1.4.2", -] - [[package]] name = "heck" version = "0.5.0" @@ -3469,26 +3298,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-http-proxy" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d06dbdfbacf34d996c6fb540a71a684a7aae9056c71951163af8a8a4c07b9a4" -dependencies = [ - "bytes", - "futures-util", - "headers", - "http 1.4.2", - "hyper 1.10.1", - "hyper-rustls 0.27.9", - "hyper-util", - "pin-project-lite", - "rustls-native-certs 0.7.3", - "tokio", - "tokio-rustls 0.26.4", - "tower-service", -] - [[package]] name = "hyper-rustls" version = "0.24.2" @@ -3514,7 +3323,7 @@ dependencies = [ "hyper 1.10.1", "hyper-util", "rustls 0.23.42", - "rustls-native-certs 0.8.4", + "rustls-native-certs", "tokio", "tokio-rustls 0.26.4", "tower-service", @@ -3990,15 +3799,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata 0.4.16", -] - [[package]] name = "md-5" version = "0.11.0" @@ -4185,15 +3985,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "num" version = "0.4.3" @@ -4343,12 +4134,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -4665,16 +4450,6 @@ dependencies = [ "toml 0.8.23", ] -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - [[package]] name = "prettyplease" version = "0.2.37" @@ -4989,7 +4764,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls 0.23.42", - "rustls-native-certs 0.8.4", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -4998,7 +4773,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tokio-util", - "tower 0.5.3", + "tower", "tower-http", "tower-service", "url", @@ -5086,15 +4861,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "roxmltree" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" -dependencies = [ - "xmlparser", -] - [[package]] name = "rust_decimal" version = "1.42.1" @@ -5196,38 +4962,16 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe 0.1.6", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.7.0", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", + "security-framework", ] [[package]] @@ -5381,19 +5125,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -5401,7 +5132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.13.1", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -5427,12 +5158,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "separator" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" - [[package]] name = "serde" version = "1.0.228" @@ -5481,7 +5206,6 @@ version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "indexmap", "itoa", "memchr", "serde", @@ -5552,6 +5276,31 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serial_test" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "settings-committer" version = "0.1.0" @@ -5944,15 +5693,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shell-words" version = "1.1.1" @@ -6438,15 +6178,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "thread_local" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" -dependencies = [ - "cfg-if", -] - [[package]] name = "time" version = "0.3.53" @@ -6740,17 +6471,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.3" @@ -6778,7 +6498,7 @@ dependencies = [ "http 1.4.2", "http-body 1.1.0", "pin-project-lite", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "url", @@ -6826,49 +6546,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata 0.4.16", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", ] [[package]] @@ -7104,12 +6781,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "version_check" version = "0.9.5" @@ -7634,12 +7305,6 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "yasna" version = "0.5.2" diff --git a/sources/Cargo.toml b/sources/Cargo.toml index 9782adbac..535de8836 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -21,7 +21,6 @@ members = [ "api/shibaken", "api/prairiedog", "api/simple-settings-plugin", - "aws-smithy-experimental", "bloodhound", @@ -91,7 +90,6 @@ members = [ [workspace.dependencies] apiclient = { version = "0.1", path = "api/apiclient", default-features = false } -aws-smithy-experimental = { version = "0.1", path = "aws-smithy-experimental" } aws-smithy-http-client = { version = "0.1", path = "aws-smithy-http-client" } block-party = { version = "0.1", path = "updater/block-party" } bottlerocket-image-features = { version = "0.1", path = "bottlerocket-image-features" } diff --git a/sources/aws-smithy-experimental/Cargo.toml b/sources/aws-smithy-experimental/Cargo.toml deleted file mode 100644 index 2d504606e..000000000 --- a/sources/aws-smithy-experimental/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "aws-smithy-experimental" -version = "0.1.4" -authors = ["AWS Rust SDK Team "] -description = "Experiments for the smithy-rs ecosystem" -edition = "2021" -license = "Apache-2.0" -publish = false - -[features] -crypto-ring = ["rustls/ring"] -crypto-aws-lc = ["rustls/aws_lc_rs"] -crypto-aws-lc-fips = ["rustls/fips"] - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(crypto_unstable)'] } - -[dependencies] -aws-smithy-types = { workspace = true, features = ["http-body-1-x"] } -aws-smithy-runtime-api = { workspace = true, features = ["client", "http-1x"] } -aws-smithy-runtime = { workspace = true, features = ["client"] } -aws-smithy-async.workspace = true -h2.workspace = true -headers.workspace = true -hyper-util.workspace = true -once_cell.workspace = true -pin-project-lite.workspace = true -rustls.workspace = true -tracing.workspace = true -tokio.workspace = true -tower.workspace = true -hyper = { workspace = true, features = ["client", "http1", "http2"] } -hyper-http-proxy.workspace = true -hyper-rustls = { workspace = true, features = ["http2", "http1", "native-tokio", "tls12"], default-features = false } -http = "1" -url.workspace = true - -[dev-dependencies] -aws-smithy-async = { workspace = true, features = ["rt-tokio", "test-util"] } -aws-smithy-runtime = { workspace = true, features = ["client", "test-util", "connector-hyper-0-14-x"]} -tokio = { workspace = true, features = ["full", "test-util"]} diff --git a/sources/aws-smithy-experimental/LICENSE b/sources/aws-smithy-experimental/LICENSE deleted file mode 100644 index 67db85882..000000000 --- a/sources/aws-smithy-experimental/LICENSE +++ /dev/null @@ -1,175 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. diff --git a/sources/aws-smithy-experimental/README.md b/sources/aws-smithy-experimental/README.md deleted file mode 100644 index a537e7ee4..000000000 --- a/sources/aws-smithy-experimental/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# aws-smithy-experimental - -See [`aws-smithy-experimental`](https://github.com/smithy-lang/smithy-rs/tree/42751e5dbf4d51c06c085e4193bf013a7333a6f5/rust-runtime/aws-smithy-experimental) - - -## Changes -- Remove `examples` and `tests` directories -- Remove `external-types.toml` -- Remove `examples` section in `Cargo.toml` -- Remove `package.metadata` section in `Cargo.toml` -- Remove `package.repository` in `Cargo.toml` -- Prevent crate from being published with `publish = false` in `Cargo.toml` -- Use workspace dependencies wherever possible -- Add linting rule to warn for missing `crypto_unstable` flag diff --git a/sources/aws-smithy-experimental/src/hyper_1_0.rs b/sources/aws-smithy-experimental/src/hyper_1_0.rs deleted file mode 100644 index 8b729b030..000000000 --- a/sources/aws-smithy-experimental/src/hyper_1_0.rs +++ /dev/null @@ -1,1322 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -use aws_smithy_async::future::timeout::TimedOutError; -use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; -use aws_smithy_runtime::client::http::connection_poisoning::CaptureSmithyConnection; -use aws_smithy_runtime_api::box_error::BoxError; -use aws_smithy_runtime_api::client::connection::ConnectionMetadata; -use aws_smithy_runtime_api::client::connector_metadata::ConnectorMetadata; -use aws_smithy_runtime_api::client::dns::ResolveDns; -use aws_smithy_runtime_api::client::http::{ - HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpClient, - SharedHttpConnector, -}; -use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; -use aws_smithy_runtime_api::client::result::ConnectorError; -use aws_smithy_runtime_api::client::runtime_components::{ - RuntimeComponents, RuntimeComponentsBuilder, -}; -use aws_smithy_runtime_api::shared::IntoShared; -use aws_smithy_types::body::SdkBody; -use aws_smithy_types::config_bag::ConfigBag; -use aws_smithy_types::error::display::DisplayErrorContext; -use aws_smithy_types::retry::ErrorKind; -use client::connect::Connection; -use h2::Reason; -use http::{Extensions, Uri}; -use hyper::rt::{Read, Write}; -use hyper_util::client::legacy as client; -use hyper_util::client::legacy::connect::dns::Name; -use hyper_util::client::legacy::connect::{ - capture_connection, CaptureConnection, Connect, HttpInfo, -}; -use hyper_util::rt::TokioExecutor; -use rustls::crypto::CryptoProvider; -use std::borrow::Cow; -use std::collections::HashMap; -use std::error::Error; -use std::future::Future; -use std::net::SocketAddr; -use std::pin::Pin; -use std::sync::RwLock; -use std::task::{Context, Poll}; -use std::time::Duration; -use std::{fmt, vec}; - -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -#[non_exhaustive] -pub enum CryptoMode { - #[cfg(feature = "crypto-ring")] - Ring, - #[cfg(feature = "crypto-aws-lc")] - AwsLc, - #[cfg(feature = "crypto-aws-lc-fips")] - AwsLcFips, -} - -impl CryptoMode { - fn provider(self) -> CryptoProvider { - match self { - #[cfg(feature = "crypto-aws-lc")] - CryptoMode::AwsLc => rustls::crypto::aws_lc_rs::default_provider(), - - #[cfg(feature = "crypto-ring")] - CryptoMode::Ring => rustls::crypto::ring::default_provider(), - - #[cfg(feature = "crypto-aws-lc-fips")] - CryptoMode::AwsLcFips => { - let provider = rustls::crypto::default_fips_provider(); - assert!( - provider.fips(), - "FIPS was requested but the provider did not support FIPS" - ); - provider - } - } - } -} - -/// A bridge that allows our `ResolveDns` trait to work with Hyper's `Resolver` interface (based on tower) -#[derive(Clone)] -struct HyperUtilResolver { - resolver: R, -} - -impl tower::Service for HyperUtilResolver { - type Response = vec::IntoIter; - type Error = Box; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Name) -> Self::Future { - let resolver = self.resolver.clone(); - Box::pin(async move { - let dns_entries = resolver.resolve_dns(req.as_str()).await?; - Ok(dns_entries - .into_iter() - .map(|ip_addr| SocketAddr::new(ip_addr, 0)) - .collect::>() - .into_iter()) - }) - } -} - -#[allow(unused_imports)] -mod cached_connectors { - use client::connect::HttpConnector; - use hyper_util::client::legacy as client; - use hyper_util::client::legacy::connect::dns::GaiResolver; - - use crate::hyper_1_0::build_connector::make_tls; - use crate::hyper_1_0::{CryptoMode, Inner}; - - #[cfg(feature = "crypto-ring")] - pub(crate) static HTTPS_NATIVE_ROOTS_RING: once_cell::sync::Lazy< - hyper_rustls::HttpsConnector, - > = once_cell::sync::Lazy::new(|| make_tls(GaiResolver::new(), CryptoMode::Ring.provider())); - - #[cfg(feature = "crypto-aws-lc")] - pub(crate) static HTTPS_NATIVE_ROOTS_AWS_LC: once_cell::sync::Lazy< - hyper_rustls::HttpsConnector, - > = once_cell::sync::Lazy::new(|| make_tls(GaiResolver::new(), CryptoMode::AwsLc.provider())); - - #[cfg(feature = "crypto-aws-lc-fips")] - pub(crate) static HTTPS_NATIVE_ROOTS_AWS_LC_FIPS: once_cell::sync::Lazy< - hyper_rustls::HttpsConnector, - > = once_cell::sync::Lazy::new(|| { - make_tls(GaiResolver::new(), CryptoMode::AwsLcFips.provider()) - }); - - pub(super) fn cached_https(mode: Inner) -> hyper_rustls::HttpsConnector { - match mode { - #[cfg(feature = "crypto-ring")] - Inner::Standard(CryptoMode::Ring) => HTTPS_NATIVE_ROOTS_RING.clone(), - #[cfg(feature = "crypto-aws-lc")] - Inner::Standard(CryptoMode::AwsLc) => HTTPS_NATIVE_ROOTS_AWS_LC.clone(), - #[cfg(feature = "crypto-aws-lc-fips")] - Inner::Standard(CryptoMode::AwsLcFips) => HTTPS_NATIVE_ROOTS_AWS_LC_FIPS.clone(), - #[allow(unreachable_patterns)] - Inner::Standard(_) => unreachable!("unexpected mode"), - Inner::Custom(provider) => make_tls(GaiResolver::new(), provider), - } - } -} - -mod build_connector { - use crate::hyper_1_0::{HyperUtilResolver, Inner}; - use aws_smithy_runtime_api::client::dns::ResolveDns; - use client::connect::HttpConnector; - use headers::Authorization; - use hyper::Uri; - use hyper_http_proxy::{Proxy, ProxyConnector}; - use hyper_util::client::legacy as client; - use rustls::crypto::CryptoProvider; - use std::sync::Arc; - use url::Url; - - fn restrict_ciphers(base: CryptoProvider) -> CryptoProvider { - let suites = &[ - rustls::CipherSuite::TLS13_AES_256_GCM_SHA384, - rustls::CipherSuite::TLS13_AES_128_GCM_SHA256, - // TLS1.2 suites - rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - rustls::CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - ]; - let supported_suites = suites - .iter() - .flat_map(|suite| { - base.cipher_suites - .iter() - .find(|s| &s.suite() == suite) - .cloned() - }) - .collect::>(); - CryptoProvider { - cipher_suites: supported_suites, - ..base - } - } - - pub(crate) fn make_tls( - resolver: R, - crypto_provider: CryptoProvider, - ) -> hyper_rustls::HttpsConnector> { - use hyper_rustls::ConfigBuilderExt; - let mut base_connector = HttpConnector::new_with_resolver(resolver); - base_connector.enforce_http(false); - hyper_rustls::HttpsConnectorBuilder::new() - .with_tls_config( - rustls::ClientConfig::builder_with_provider(Arc::new(restrict_ciphers(crypto_provider))) - .with_safe_default_protocol_versions() - .expect("Error with the TLS configuration. Please file a bug report under https://github.com/smithy-lang/smithy-rs/issues.") - .with_native_roots().expect("error with TLS configuration.") - .with_no_client_auth() - ) - .https_or_http() - .enable_http1() - .enable_http2() - .wrap_connector(base_connector) - } - - pub(super) fn https_with_resolver( - crypto_provider: Inner, - resolver: R, - ) -> hyper_rustls::HttpsConnector>> { - make_tls(HyperUtilResolver { resolver }, crypto_provider.provider()) - } - - pub(super) fn https_with_proxy( - https_connector: hyper_rustls::HttpsConnector, - https_proxy: &str, - no_proxy: Option>, - ) -> hyper_http_proxy::ProxyConnector> { - // Determines whether a request of a given scheme, host and port should be proxied - // according to `https_proxy` and `no_proxy`. - - let intercept = move |scheme: Option<&str>, host: Option<&str>, _port| { - if let Some(host) = host { - if let Some(no_proxy) = &no_proxy { - if scheme != Some("https") { - return false; - } - if no_proxy.iter().any(|s| s == "*") { - // Don't proxy anything - return false; - } - // If the host matches one of the no proxy list entries, return false (don't proxy) - // Note that we're not doing anything fancy here for checking `no_proxy` since - // we only expect requests here to be going out to some AWS API endpoint. - return !no_proxy.iter().any(|no_proxy_host| { - !no_proxy_host.is_empty() && host.ends_with(no_proxy_host) - }); - } - true - } else { - false - } - }; - - let mut proxy_uri = https_proxy.parse::().expect("Invalid proxy URI"); - - // If the proxy's URI doesn't have a scheme, assume HTTP for the scheme and let the proxy - // server forward HTTPS connections and start a tunnel. - if proxy_uri.scheme().is_none() { - proxy_uri = format!("http://{https_proxy}") - .parse::() - .expect("Unable to parse proxy URI as HTTPS"); - } - let mut proxy = Proxy::new(intercept, proxy_uri); - // Parse https_proxy as URL to extract out auth information if any - let proxy_url = - Url::parse(&proxy.uri().to_string()).expect("Unable to parse HTTPS proxy as URL"); - - if !proxy_url.username().is_empty() || proxy_url.password().is_some() { - proxy.set_authorization(Authorization::basic( - proxy_url.username(), - proxy_url.password().unwrap_or_default(), - )); - } - ProxyConnector::from_proxy(https_connector, proxy) - .expect("Failed to create proxy connector") - } -} - -/// [`HttpConnector`] that uses [`hyper`] to make HTTP requests. -/// -/// This connector also implements socket connect and read timeouts. -/// -/// This shouldn't be used directly in most cases. -/// See the docs on [`HyperClientBuilder`] for examples of how -/// to customize the Hyper client. -#[derive(Debug)] -pub struct HyperConnector { - adapter: Box, -} - -impl HyperConnector { - /// Builder for a Hyper connector. - pub fn builder() -> HyperConnectorBuilder { - Default::default() - } -} - -impl HttpConnector for HyperConnector { - fn call(&self, request: HttpRequest) -> HttpConnectorFuture { - self.adapter.call(request) - } -} - -/// Builder for [`HyperConnector`]. -#[derive(Default, Debug)] -pub struct HyperConnectorBuilder { - connector_settings: Option, - sleep_impl: Option, - client_builder: Option, - #[allow(unused)] - crypto: Crypto, -} - -#[derive(Default)] -#[non_exhaustive] -pub struct CryptoUnset {} - -pub struct CryptoProviderSelected { - crypto_provider: Inner, -} - -#[derive(Clone)] -enum Inner { - Standard(CryptoMode), - #[allow(dead_code)] - Custom(CryptoProvider), -} - -impl Inner { - fn provider(&self) -> CryptoProvider { - match self { - Inner::Standard(mode) => mode.provider(), - Inner::Custom(provider) => provider.clone(), - } - } -} - -#[cfg(any(feature = "crypto-aws-lc", feature = "crypto-ring"))] -impl HyperConnectorBuilder { - pub fn build_from_resolver( - self, - resolver: R, - ) -> HyperConnector { - let connector = - build_connector::https_with_resolver(self.crypto.crypto_provider.clone(), resolver); - self.build(connector) - } -} - -impl HyperConnectorBuilder { - /// Create a [`HyperConnector`] from this builder and a given connector. - pub(crate) fn build(self, tcp_connector: C) -> HyperConnector - where - C: Send + Sync + 'static, - C: Clone, - C: tower::Service, - C::Response: Read + Write + Connection + Send + Sync + Unpin, - C: Connect, - C::Future: Unpin + Send + 'static, - C::Error: Into, - { - let client_builder = - self.client_builder - .unwrap_or(hyper_util::client::legacy::Builder::new( - TokioExecutor::new(), - )); - let sleep_impl = self.sleep_impl.or_else(default_async_sleep); - let (connect_timeout, read_timeout) = self - .connector_settings - .map(|c| (c.connect_timeout(), c.read_timeout())) - .unwrap_or((None, None)); - - let connector = match connect_timeout { - Some(duration) => timeout_middleware::ConnectTimeout::new( - tcp_connector, - sleep_impl - .clone() - .expect("a sleep impl must be provided in order to have a connect timeout"), - duration, - ), - None => timeout_middleware::ConnectTimeout::no_timeout(tcp_connector), - }; - let base = client_builder.build(connector); - let read_timeout = match read_timeout { - Some(duration) => timeout_middleware::HttpReadTimeout::new( - base, - sleep_impl.expect("a sleep impl must be provided in order to have a read timeout"), - duration, - ), - None => timeout_middleware::HttpReadTimeout::no_timeout(base), - }; - HyperConnector { - adapter: Box::new(Adapter { - client: read_timeout, - }), - } - } - - /// Set the async sleep implementation used for timeouts - /// - /// Calling this is only necessary for testing or to use something other than - /// [`default_async_sleep`]. - pub fn sleep_impl(mut self, sleep_impl: impl AsyncSleep + 'static) -> Self { - self.sleep_impl = Some(sleep_impl.into_shared()); - self - } - - /// Set the async sleep implementation used for timeouts - /// - /// Calling this is only necessary for testing or to use something other than - /// [`default_async_sleep`]. - pub fn set_sleep_impl(&mut self, sleep_impl: Option) -> &mut Self { - self.sleep_impl = sleep_impl; - self - } - - /// Configure the HTTP settings for the `HyperAdapter` - pub fn connector_settings(mut self, connector_settings: HttpConnectorSettings) -> Self { - self.connector_settings = Some(connector_settings); - self - } - - /// Configure the HTTP settings for the `HyperAdapter` - pub fn set_connector_settings( - &mut self, - connector_settings: Option, - ) -> &mut Self { - self.connector_settings = connector_settings; - self - } - - /// Override the Hyper client [`Builder`](hyper_util::client::legacy::Builder) used to construct this client. - /// - /// This enables changing settings like forcing HTTP2 and modifying other default client behavior. - pub(crate) fn hyper_builder( - mut self, - hyper_builder: hyper_util::client::legacy::Builder, - ) -> Self { - self.set_hyper_builder(Some(hyper_builder)); - self - } - - /// Override the Hyper client [`Builder`](hyper_util::client::legacy::Builder) used to construct this client. - /// - /// This enables changing settings like forcing HTTP2 and modifying other default client behavior. - pub(crate) fn set_hyper_builder( - &mut self, - hyper_builder: Option, - ) -> &mut Self { - self.client_builder = hyper_builder; - self - } -} - -/// Adapter to use a Hyper 1.0-based Client as an `HttpConnector` -/// -/// This adapter also enables TCP `CONNECT` and HTTP `READ` timeouts via [`HyperConnector::builder`]. -struct Adapter { - client: timeout_middleware::HttpReadTimeout< - hyper_util::client::legacy::Client, SdkBody>, - >, -} - -impl fmt::Debug for Adapter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Adapter") - .field("client", &"** hyper client **") - .finish() - } -} - -/// Extract a smithy connection from a hyper CaptureConnection -fn extract_smithy_connection(capture_conn: &CaptureConnection) -> Option { - let capture_conn = capture_conn.clone(); - if let Some(conn) = capture_conn.clone().connection_metadata().as_ref() { - let mut extensions = Extensions::new(); - conn.get_extras(&mut extensions); - let http_info = extensions.get::(); - let mut builder = ConnectionMetadata::builder() - .proxied(conn.is_proxied()) - .poison_fn(move || match capture_conn.connection_metadata().as_ref() { - Some(conn) => conn.poison(), - None => tracing::trace!("no connection existed to poison"), - }); - - builder - .set_local_addr(http_info.map(|info| info.local_addr())) - .set_remote_addr(http_info.map(|info| info.remote_addr())); - - let smithy_connection = builder.build(); - - Some(smithy_connection) - } else { - None - } -} - -impl HttpConnector for Adapter -where - C: Clone + Send + Sync + 'static, - C: tower::Service, - C::Response: Connection + Read + Write + Unpin + 'static, - timeout_middleware::ConnectTimeout: Connect, - C::Future: Unpin + Send + 'static, - C::Error: Into, -{ - fn call(&self, request: HttpRequest) -> HttpConnectorFuture { - let mut request = match request.try_into_http1x() { - Ok(request) => request, - Err(err) => { - return HttpConnectorFuture::ready(Err(ConnectorError::user(err.into()))); - } - }; - let capture_connection = capture_connection(&mut request); - if let Some(capture_smithy_connection) = - request.extensions().get::() - { - capture_smithy_connection - .set_connection_retriever(move || extract_smithy_connection(&capture_connection)); - } - let mut client = self.client.clone(); - use tower::Service; - let fut = client.call(request); - HttpConnectorFuture::new(async move { - let response = fut - .await - .map_err(downcast_error)? - .map(SdkBody::from_body_1_x); - match HttpResponse::try_from(response) { - Ok(response) => Ok(response), - Err(err) => Err(ConnectorError::other(err.into(), None)), - } - }) - } -} - -/// Downcast errors coming out of hyper into an appropriate `ConnectorError` -fn downcast_error(err: BoxError) -> ConnectorError { - // is a `TimedOutError` (from aws_smithy_async::timeout) in the chain? if it is, this is a timeout - if find_source::(err.as_ref()).is_some() { - return ConnectorError::timeout(err); - } - // is the top of chain error actually already a `ConnectorError`? return that directly - let err = match err.downcast::() { - Ok(connector_error) => return *connector_error, - Err(box_error) => box_error, - }; - // generally, the top of chain will probably be a hyper error. Go through a set of hyper specific - // error classifications - let err = match find_source::(err.as_ref()) { - Some(hyper_error) => return to_connector_error(hyper_error)(err), - None => err, - }; - - // otherwise, we have no idea! - ConnectorError::other(err, None) -} - -/// Convert a [`hyper::Error`] into a [`ConnectorError`] -fn to_connector_error(err: &hyper::Error) -> fn(BoxError) -> ConnectorError { - if err.is_timeout() || find_source::(err).is_some() { - return ConnectorError::timeout; - } - if err.is_user() { - return ConnectorError::user; - } - if err.is_closed() || err.is_canceled() || find_source::(err).is_some() { - return ConnectorError::io; - } - // We sometimes receive this from S3: hyper::Error(IncompleteMessage) - if err.is_incomplete_message() { - return |err: BoxError| ConnectorError::other(err, Some(ErrorKind::TransientError)); - } - - if let Some(h2_err) = find_source::(err) { - if h2_err.is_go_away() - || (h2_err.is_reset() && h2_err.reason() == Some(Reason::REFUSED_STREAM)) - { - return ConnectorError::io; - } - } - - tracing::warn!(err = %DisplayErrorContext(&err), "unrecognized error from Hyper. If this error should be retried, please file an issue."); - |err: BoxError| ConnectorError::other(err, None) -} - -fn find_source<'a, E: Error + 'static>(err: &'a (dyn Error + 'static)) -> Option<&'a E> { - let mut next = Some(err); - while let Some(err) = next { - if let Some(matching_err) = err.downcast_ref::() { - return Some(matching_err); - } - next = err.source(); - } - None -} - -// TODO(https://github.com/awslabs/aws-sdk-rust/issues/1090): CacheKey must also include ptr equality to any -// runtime components that are used—sleep_impl as a base (unless we prohibit overriding sleep impl) -// If we decide to put a DnsResolver in RuntimeComponents, then we'll need to handle that as well. -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -struct CacheKey { - connect_timeout: Option, - read_timeout: Option, -} - -impl From<&HttpConnectorSettings> for CacheKey { - fn from(value: &HttpConnectorSettings) -> Self { - Self { - connect_timeout: value.connect_timeout(), - read_timeout: value.read_timeout(), - } - } -} - -struct HyperClient { - connector_cache: RwLock>, - client_builder: hyper_util::client::legacy::Builder, - tcp_connector_fn: F, -} - -impl fmt::Debug for HyperClient { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("HyperClient") - .field("connector_cache", &self.connector_cache) - .field("client_builder", &self.client_builder) - .finish() - } -} - -impl HttpClient for HyperClient -where - F: Fn() -> C + Send + Sync, - C: Clone + Send + Sync + 'static, - C: tower::Service, - C::Response: Connection + Read + Write + Send + Sync + Unpin + 'static, - C::Future: Unpin + Send + 'static, - C::Error: Into, -{ - fn http_connector( - &self, - settings: &HttpConnectorSettings, - components: &RuntimeComponents, - ) -> SharedHttpConnector { - let key = CacheKey::from(settings); - let mut connector = self.connector_cache.read().unwrap().get(&key).cloned(); - if connector.is_none() { - let mut cache = self.connector_cache.write().unwrap(); - // Short-circuit if another thread already wrote a connector to the cache for this key - if !cache.contains_key(&key) { - let mut builder = HyperConnector::builder() - .hyper_builder(self.client_builder.clone()) - .connector_settings(settings.clone()); - builder.set_sleep_impl(components.sleep_impl()); - - let start = components.time_source().map(|ts| ts.now()); - let tcp_connector = (self.tcp_connector_fn)(); - let end = components.time_source().map(|ts| ts.now()); - if let (Some(start), Some(end)) = (start, end) { - if let Ok(elapsed) = end.duration_since(start) { - tracing::debug!("new TCP connector created in {:?}", elapsed); - } - } - let connector = SharedHttpConnector::new(builder.build(tcp_connector)); - cache.insert(key.clone(), connector); - } - connector = cache.get(&key).cloned(); - } - - connector.expect("cache populated above") - } - - fn validate_base_client_config( - &self, - _: &RuntimeComponentsBuilder, - _: &ConfigBag, - ) -> Result<(), BoxError> { - // Initialize the TCP connector at this point so that native certs load - // at client initialization time instead of upon first request. We do it - // here rather than at construction so that it won't run if this is not - // the selected HTTP client for the base config (for example, if this was - // the default HTTP client, and it was overridden by a later plugin). - let _ = (self.tcp_connector_fn)(); - Ok(()) - } - - fn connector_metadata(&self) -> Option { - Some(ConnectorMetadata::new("hyper", Some(Cow::Borrowed("1.x")))) - } -} - -/// Builder for a hyper-backed [`HttpClient`] implementation. -/// -/// This builder can be used to customize the underlying TCP connector used, as well as -/// hyper client configuration. -/// -/// # Examples -/// -/// Construct a Hyper client with the RusTLS TLS implementation. -/// This can be useful when you want to share a Hyper connector between multiple -/// generated Smithy clients. -#[derive(Clone, Default, Debug)] -pub struct HyperClientBuilder { - client_builder: Option, - crypto_provider: Crypto, -} - -impl HyperClientBuilder { - /// Create a hyper client using RusTLS for TLS - /// - /// The trusted certificates will be loaded later when this becomes the selected - /// HTTP client for a Smithy client. - pub fn build_https(self) -> SharedHttpClient { - let crypto = self.crypto_provider.crypto_provider; - build_with_fn(self.client_builder, move || { - cached_connectors::cached_https(crypto.clone()) - }) - } - - /// Create a hyper client using a custom DNS resolver - pub fn build_with_resolver( - self, - resolver: impl ResolveDns + Clone + 'static, - ) -> SharedHttpClient { - build_with_fn(self.client_builder, move || { - build_connector::https_with_resolver( - self.crypto_provider.crypto_provider.clone(), - resolver.clone(), - ) - }) - } - - /// Create a hyper client using a proxy connector - pub fn build_with_proxy(self, https_proxy: H, no_proxy: Option<&[N]>) -> SharedHttpClient - where - H: AsRef + Clone + Send + Sync + 'static, - N: AsRef, - { - let crypto = self.crypto_provider.crypto_provider; - let no_proxy: Option> = - no_proxy.map(|n| n.iter().map(|s| s.as_ref().to_owned()).collect()); - build_with_fn(self.client_builder, move || { - build_connector::https_with_proxy( - cached_connectors::cached_https(crypto.clone()), - https_proxy.as_ref(), - no_proxy.clone(), - ) - }) - } -} - -impl HyperClientBuilder { - /// Creates a new builder. - pub fn new() -> Self { - Self::default() - } - - pub fn crypto_mode(self, provider: CryptoMode) -> HyperClientBuilder { - HyperClientBuilder { - client_builder: self.client_builder, - crypto_provider: CryptoProviderSelected { - crypto_provider: Inner::Standard(provider), - }, - } - } - - /// This interface will be broken in the future - /// - /// This exposes `CryptoProvider` from `rustls` directly and this API has no stability guarantee. - #[cfg(crypto_unstable)] - pub fn crypto_provider_unstable( - self, - provider: CryptoProvider, - ) -> HyperClientBuilder { - HyperClientBuilder { - client_builder: self.client_builder, - crypto_provider: CryptoProviderSelected { - crypto_provider: Inner::Custom(provider), - }, - } - } -} - -fn build_with_fn( - client_builder: Option, - tcp_connector_fn: F, -) -> SharedHttpClient -where - F: Fn() -> C + Send + Sync + 'static, - C: Clone + Send + Sync + 'static, - C: tower::Service, - C::Response: Connection + Read + Write + Send + Sync + Unpin + 'static, - C::Future: Unpin + Send + 'static, - C::Error: Into, - C: Connect, -{ - SharedHttpClient::new(HyperClient { - connector_cache: RwLock::new(HashMap::new()), - client_builder: client_builder - .unwrap_or_else(|| hyper_util::client::legacy::Builder::new(TokioExecutor::new())), - tcp_connector_fn, - }) -} - -mod timeout_middleware { - use std::error::Error; - use std::fmt::Formatter; - use std::future::Future; - use std::pin::Pin; - use std::task::{Context, Poll}; - use std::time::Duration; - - use http::Uri; - use pin_project_lite::pin_project; - - use aws_smithy_async::future::timeout::{TimedOutError, Timeout}; - use aws_smithy_async::rt::sleep::Sleep; - use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; - use aws_smithy_runtime_api::box_error::BoxError; - - #[derive(Debug)] - pub(crate) struct HttpTimeoutError { - kind: &'static str, - duration: Duration, - } - - impl std::fmt::Display for HttpTimeoutError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} timeout occurred after {:?}", - self.kind, self.duration - ) - } - } - - impl Error for HttpTimeoutError { - // We implement the `source` function as returning a `TimedOutError` because when `downcast_error` - // or `find_source` is called with an `HttpTimeoutError` (or another error wrapping an `HttpTimeoutError`) - // this method will be checked to determine if it's a timeout-related error. - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&TimedOutError) - } - } - - /// Timeout wrapper that will timeout on the initial TCP connection - /// - /// # Stability - /// This interface is unstable. - #[derive(Clone, Debug)] - pub(super) struct ConnectTimeout { - inner: I, - timeout: Option<(SharedAsyncSleep, Duration)>, - } - - impl ConnectTimeout { - /// Create a new `ConnectTimeout` around `inner`. - /// - /// Typically, `I` will implement [`hyper_util::client::legacy::connect::Connect`]. - pub(crate) fn new(inner: I, sleep: SharedAsyncSleep, timeout: Duration) -> Self { - Self { - inner, - timeout: Some((sleep, timeout)), - } - } - - pub(crate) fn no_timeout(inner: I) -> Self { - Self { - inner, - timeout: None, - } - } - } - - #[derive(Clone, Debug)] - pub(crate) struct HttpReadTimeout { - inner: I, - timeout: Option<(SharedAsyncSleep, Duration)>, - } - - impl HttpReadTimeout { - /// Create a new `HttpReadTimeout` around `inner`. - /// - /// Typically, `I` will implement [`tower::Service>`]. - pub(crate) fn new(inner: I, sleep: SharedAsyncSleep, timeout: Duration) -> Self { - Self { - inner, - timeout: Some((sleep, timeout)), - } - } - - pub(crate) fn no_timeout(inner: I) -> Self { - Self { - inner, - timeout: None, - } - } - } - - pin_project! { - /// Timeout future for Tower services - /// - /// Timeout future to handle timing out, mapping errors, and the possibility of not timing out - /// without incurring an additional allocation for each timeout layer. - #[project = MaybeTimeoutFutureProj] - pub enum MaybeTimeoutFuture { - Timeout { - #[pin] - timeout: Timeout, - error_type: &'static str, - duration: Duration, - }, - NoTimeout { - #[pin] - future: F - } - } - } - - impl Future for MaybeTimeoutFuture - where - F: Future>, - E: Into, - { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let (timeout_future, kind, &mut duration) = match self.project() { - MaybeTimeoutFutureProj::NoTimeout { future } => { - return future.poll(cx).map_err(|err| err.into()); - } - MaybeTimeoutFutureProj::Timeout { - timeout, - error_type, - duration, - } => (timeout, error_type, duration), - }; - match timeout_future.poll(cx) { - Poll::Ready(Ok(response)) => Poll::Ready(response.map_err(|err| err.into())), - Poll::Ready(Err(_timeout)) => { - Poll::Ready(Err(HttpTimeoutError { kind, duration }.into())) - } - Poll::Pending => Poll::Pending, - } - } - } - - impl tower::Service for ConnectTimeout - where - I: tower::Service, - I::Error: Into, - { - type Response = I::Response; - type Error = BoxError; - type Future = MaybeTimeoutFuture; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx).map_err(|err| err.into()) - } - - fn call(&mut self, req: Uri) -> Self::Future { - match &self.timeout { - Some((sleep, duration)) => { - let sleep = sleep.sleep(*duration); - MaybeTimeoutFuture::Timeout { - timeout: Timeout::new(self.inner.call(req), sleep), - error_type: "HTTP connect", - duration: *duration, - } - } - None => MaybeTimeoutFuture::NoTimeout { - future: self.inner.call(req), - }, - } - } - } - - impl tower::Service> for HttpReadTimeout - where - I: tower::Service>, - I::Error: Send + Sync + Error + 'static, - { - type Response = I::Response; - type Error = BoxError; - type Future = MaybeTimeoutFuture; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx).map_err(|err| err.into()) - } - - fn call(&mut self, req: http::Request) -> Self::Future { - match &self.timeout { - Some((sleep, duration)) => { - let sleep = sleep.sleep(*duration); - MaybeTimeoutFuture::Timeout { - timeout: Timeout::new(self.inner.call(req), sleep), - error_type: "HTTP read", - duration: *duration, - } - } - None => MaybeTimeoutFuture::NoTimeout { - future: self.inner.call(req), - }, - } - } - } - - #[cfg(test)] - pub(crate) mod test { - use std::time::Duration; - - use hyper::rt::ReadBufCursor; - use hyper_util::client::legacy::connect::Connected; - use hyper_util::rt::TokioIo; - use tokio::net::TcpStream; - - use aws_smithy_async::assert_elapsed; - use aws_smithy_async::future::never::Never; - use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; - use aws_smithy_types::error::display::DisplayErrorContext; - - use super::super::*; - - #[allow(unused)] - fn connect_timeout_is_correct() { - is_send_sync::>(); - } - - #[allow(unused)] - fn is_send_sync() {} - - /// A service that will never return whatever it is you want - /// - /// Returned futures will return Pending forever - #[non_exhaustive] - #[derive(Clone, Default, Debug)] - pub(crate) struct NeverConnects; - impl tower::Service for NeverConnects { - type Response = TokioIo; - type Error = ConnectorError; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _uri: Uri) -> Self::Future { - Box::pin(async move { - Never::new().await; - unreachable!() - }) - } - } - - /// A service that will connect but never send any data - #[derive(Clone, Debug, Default)] - struct NeverReplies; - impl tower::Service for NeverReplies { - type Response = EmptyStream; - type Error = BoxError; - type Future = std::future::Ready>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _req: Uri) -> Self::Future { - std::future::ready(Ok(EmptyStream)) - } - } - - /// A stream that will never return or accept any data - #[non_exhaustive] - #[derive(Debug, Default)] - struct EmptyStream; - impl Read for EmptyStream { - fn poll_read( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - _buf: ReadBufCursor<'_>, - ) -> Poll> { - Poll::Pending - } - } - impl Write for EmptyStream { - fn poll_write( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - _buf: &[u8], - ) -> Poll> { - Poll::Pending - } - - fn poll_flush( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Pending - } - - fn poll_shutdown( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Pending - } - } - impl Connection for EmptyStream { - fn connected(&self) -> Connected { - Connected::new() - } - } - - #[tokio::test] - async fn http_connect_timeout_works() { - let tcp_connector = NeverConnects::default(); - let connector_settings = HttpConnectorSettings::builder() - .connect_timeout(Duration::from_secs(1)) - .build(); - let hyper = HyperConnector::builder() - .connector_settings(connector_settings) - .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) - .build(tcp_connector) - .adapter; - let now = tokio::time::Instant::now(); - tokio::time::pause(); - let resp = hyper - .call(HttpRequest::get("https://static-uri.com").unwrap()) - .await - .unwrap_err(); - assert!( - resp.is_timeout(), - "expected resp.is_timeout() to be true but it was false, resp == {resp:?}" - ); - let message = DisplayErrorContext(&resp).to_string(); - let expected = - "timeout: client error (Connect): HTTP connect timeout occurred after 1s"; - assert!( - message.contains(expected), - "expected '{message}' to contain '{expected}'" - ); - assert_elapsed!(now, Duration::from_secs(1)); - } - - #[tokio::test] - async fn http_read_timeout_works() { - let tcp_connector = NeverReplies; - let connector_settings = HttpConnectorSettings::builder() - .connect_timeout(Duration::from_secs(1)) - .read_timeout(Duration::from_secs(2)) - .build(); - let hyper = HyperConnector::builder() - .connector_settings(connector_settings) - .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) - .build(tcp_connector) - .adapter; - let now = tokio::time::Instant::now(); - tokio::time::pause(); - let err = hyper - .call(HttpRequest::get("https://fake-uri.com").unwrap()) - .await - .unwrap_err(); - assert!( - err.is_timeout(), - "expected err.is_timeout() to be true but it was false, err == {err:?}", - ); - let message = format!("{}", DisplayErrorContext(&err)); - let expected = "timeout: HTTP read timeout occurred after 2s"; - assert!( - message.contains(expected), - "expected '{message}' to contain '{expected}'" - ); - assert_elapsed!(now, Duration::from_secs(2)); - } - } -} - -#[cfg(test)] -mod test { - use std::io::{Error, ErrorKind}; - use std::pin::Pin; - use std::sync::atomic::{AtomicU32, Ordering}; - use std::sync::Arc; - use std::task::{Context, Poll}; - - use http::Uri; - use hyper::rt::ReadBufCursor; - use hyper_util::client::legacy::connect::Connected; - - use aws_smithy_async::time::SystemTimeSource; - use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; - - use crate::hyper_1_0::timeout_middleware::test::NeverConnects; - - use super::*; - - #[tokio::test] - async fn connector_selection() { - // Create a client that increments a count every time it creates a new HyperConnector - let creation_count = Arc::new(AtomicU32::new(0)); - let http_client = build_with_fn(None, { - let count = creation_count.clone(); - move || { - count.fetch_add(1, Ordering::Relaxed); - NeverConnects - } - }); - - // This configuration should result in 4 separate connectors with different timeout settings - let settings = [ - HttpConnectorSettings::builder() - .connect_timeout(Duration::from_secs(3)) - .build(), - HttpConnectorSettings::builder() - .read_timeout(Duration::from_secs(3)) - .build(), - HttpConnectorSettings::builder() - .connect_timeout(Duration::from_secs(3)) - .read_timeout(Duration::from_secs(3)) - .build(), - HttpConnectorSettings::builder() - .connect_timeout(Duration::from_secs(5)) - .read_timeout(Duration::from_secs(3)) - .build(), - ]; - - // Kick off thousands of parallel tasks that will try to create a connector - let components = RuntimeComponentsBuilder::for_tests() - .with_time_source(Some(SystemTimeSource::new())) - .build() - .unwrap(); - let mut handles = Vec::new(); - for setting in &settings { - for _ in 0..1000 { - let client = http_client.clone(); - handles.push(tokio::spawn({ - let setting = setting.clone(); - let components = components.clone(); - async move { - let _ = client.http_connector(&setting, &components); - } - })); - } - } - for handle in handles { - handle.await.unwrap(); - } - - // Verify only 4 connectors were created amidst the chaos - assert_eq!(4, creation_count.load(Ordering::Relaxed)); - } - - #[tokio::test] - async fn hyper_io_error() { - let connector = TestConnection { - inner: HangupStream, - }; - let adapter = HyperConnector::builder().build(connector).adapter; - let err = adapter - .call(HttpRequest::get("https://socket-hangup.com").unwrap()) - .await - .expect_err("socket hangup"); - assert!(err.is_io(), "unexpected error type: {err:?}"); - } - - // ---- machinery to make a Hyper connector that responds with an IO Error - #[derive(Clone)] - struct HangupStream; - - impl Connection for HangupStream { - fn connected(&self) -> Connected { - Connected::new() - } - } - - impl Read for HangupStream { - fn poll_read( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - _buf: ReadBufCursor<'_>, - ) -> Poll> { - Poll::Ready(Err(Error::new( - ErrorKind::ConnectionReset, - "connection reset", - ))) - } - } - - impl Write for HangupStream { - fn poll_write( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - _buf: &[u8], - ) -> Poll> { - Poll::Pending - } - - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Pending - } - - fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Pending - } - } - - #[derive(Clone)] - struct TestConnection { - inner: T, - } - - impl tower::Service for TestConnection - where - T: Clone + Connection, - { - type Response = T; - type Error = BoxError; - type Future = std::future::Ready>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _req: Uri) -> Self::Future { - std::future::ready(Ok(self.inner.clone())) - } - } -} diff --git a/sources/aws-smithy-experimental/src/lib.rs b/sources/aws-smithy-experimental/src/lib.rs deleted file mode 100644 index fc0bf781e..000000000 --- a/sources/aws-smithy-experimental/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/* Automatically managed default lints */ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] -/* End of automatically managed default lints */ - -pub mod hyper_1_0; diff --git a/sources/aws-smithy-http-client/Cargo.toml b/sources/aws-smithy-http-client/Cargo.toml index 75004241d..1d7d4efa2 100644 --- a/sources/aws-smithy-http-client/Cargo.toml +++ b/sources/aws-smithy-http-client/Cargo.toml @@ -31,3 +31,6 @@ tokio-rustls = { version = "0.26", default-features = false, optional = true } tower = { version = "0.5", optional = true } tracing = "0.1" url = { workspace = true } + +[dev-dependencies] +serial_test = "3" diff --git a/sources/deny.toml b/sources/deny.toml index eddb0206b..0f276283c 100644 --- a/sources/deny.toml +++ b/sources/deny.toml @@ -48,7 +48,14 @@ wildcards = "deny" deny = [{ name = "structopt" }, { name = "clap", wrappers = ["cargo-readme"] }] -skip = [] +skip = [ + # `aws-sigv4 v1.2.6` depends on both `crypto-bigint v0.5.5` and `crypto-bigint v0.4.9` + { name = "crypto-bigint", version = "=0.4.9" }, + # Older crates (gptman, handlebars, tungstenite) use thiserror 1.x while + # newer crates (asn1-rs, x509-parser, cached) use thiserror 2.x + { name = "thiserror", version = "=1" }, + { name = "thiserror-impl", version = "=1" }, +] skip-tree = [ # windows-sys is not a direct dependency. mio and schannel # are using different versions of windows-sys. we skip the @@ -57,9 +64,9 @@ skip-tree = [ { name = "windows-sys" }, # httptest uses an older version of bstr { name = "httptest", version = "=0.15" }, - # aws-smithy-experimental brings a set of new dependencies that - # the aws-sdk doesn't support - { name = "aws-smithy-experimental", version = "=0.1" }, + # aws-smithy-http-client (local carry) brings newer TLS dependencies + # that coexist with older versions in the aws-sdk dependency tree + { name = "aws-smithy-http-client", version = "=0.1" }, # actix-http has upgraded to v0.9 of 'rand' but upstream has called out some # breaking changes which will need to be explored for our first party code # https://rust-random.github.io/book/update-0.9.html From 2033f69084a45ad4db71b85b8cad054ba4d7b69d Mon Sep 17 00:00:00 2001 From: Jingwei Wang Date: Mon, 8 Jun 2026 17:33:35 +0000 Subject: [PATCH 7/8] pluto: use bottlerocket-crypto-provider for runtime FIPS detection Replace compile-time FIPS crypto selection with runtime detection via bottlerocket-crypto-provider. The provider reads /proc/sys/crypto/fips_enabled at startup and selects FIPS-restricted or default cipher suites accordingly. The direct aws-lc-rs and rustls dependencies are removed since they are now provided transitively through bottlerocket-crypto-provider. Signed-off-by: Jingwei Wang --- sources/Cargo.lock | 3 +-- sources/Cargo.toml | 1 + sources/api/pluto/Cargo.toml | 5 ++--- sources/api/pluto/src/ec2.rs | 6 +++--- sources/api/pluto/src/eks.rs | 6 +++--- sources/api/pluto/src/main.rs | 15 +++++++++------ 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 1cc868480..106250fb4 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -4364,7 +4364,6 @@ version = "0.1.0" dependencies = [ "argh", "aws-config", - "aws-lc-rs", "aws-sdk-ec2", "aws-sdk-eks", "aws-smithy-http-client 0.1.0", @@ -4372,6 +4371,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "base64", + "bottlerocket-crypto-provider", "bottlerocket-modeled-types", "bottlerocket-settings-models", "constants", @@ -4379,7 +4379,6 @@ dependencies = [ "httptest", "imdsclient", "log", - "rustls 0.23.42", "serde", "serde_json", "simplelog", diff --git a/sources/Cargo.toml b/sources/Cargo.toml index 535de8836..4c1a60607 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -92,6 +92,7 @@ members = [ apiclient = { version = "0.1", path = "api/apiclient", default-features = false } aws-smithy-http-client = { version = "0.1", path = "aws-smithy-http-client" } block-party = { version = "0.1", path = "updater/block-party" } +bottlerocket-crypto-provider = { version = "0.1", path = "bottlerocket-crypto-provider" } bottlerocket-image-features = { version = "0.1", path = "bottlerocket-image-features" } bottlerocket-release = { version = "0.1", path = "bottlerocket-release" } constants = { version = "0.1", path = "constants" } diff --git a/sources/api/pluto/Cargo.toml b/sources/api/pluto/Cargo.toml index ef2919e17..9b5aec571 100644 --- a/sources/api/pluto/Cargo.toml +++ b/sources/api/pluto/Cargo.toml @@ -10,12 +10,11 @@ build = "build.rs" exclude = ["README.md"] [features] -fips = ["aws-lc-rs/fips", "aws-smithy-http-client/rustls-aws-lc-fips"] +fips = ["aws-smithy-http-client/rustls-aws-lc-fips"] [dependencies] argh.workspace = true aws-config.workspace = true -aws-lc-rs = { workspace = true, features = ["bindgen"] } aws-sdk-eks.workspace = true aws-sdk-ec2.workspace = true aws-smithy-http-client = { workspace = true, features = ["rustls-aws-lc"] } @@ -28,7 +27,7 @@ bottlerocket-settings-models.workspace = true constants.workspace = true imdsclient.workspace = true log.workspace = true -rustls.workspace = true +bottlerocket-crypto-provider.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true simplelog.workspace = true diff --git a/sources/api/pluto/src/ec2.rs b/sources/api/pluto/src/ec2.rs index 93dce35f0..a056ecbef 100644 --- a/sources/api/pluto/src/ec2.rs +++ b/sources/api/pluto/src/ec2.rs @@ -1,5 +1,5 @@ use crate::aws::sdk_config; -use crate::PROVIDER; +use crate::crypto_mode; use aws_smithy_http_client::{proxy::ProxyConfig, tls, Builder as HttpClientBuilder, Connector}; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::error::metadata::ProvideErrorMetadata; @@ -126,13 +126,13 @@ where HttpClientBuilder::new().build_with_connector_fn(move |settings, _runtime_components| { let mut builder = Connector::builder() .proxy_config(proxy.clone()) - .tls_provider(tls::Provider::Rustls(PROVIDER.clone())); + .tls_provider(tls::Provider::Rustls(crypto_mode())); builder.set_connector_settings(settings.cloned()); builder.build() }) } else { HttpClientBuilder::new() - .tls_provider(tls::Provider::Rustls(PROVIDER.clone())) + .tls_provider(tls::Provider::Rustls(crypto_mode())) .build_https() }; let ec2_config = aws_sdk_ec2::config::Builder::from(&config) diff --git a/sources/api/pluto/src/eks.rs b/sources/api/pluto/src/eks.rs index a36c337f0..084bba614 100644 --- a/sources/api/pluto/src/eks.rs +++ b/sources/api/pluto/src/eks.rs @@ -1,5 +1,5 @@ use crate::aws::sdk_config; -use crate::PROVIDER; +use crate::crypto_mode; use aws_sdk_eks::types::KubernetesNetworkConfigResponse; use aws_smithy_http_client::{proxy::ProxyConfig, tls, Builder as HttpClientBuilder, Connector}; use aws_smithy_types::error::display::DisplayErrorContext; @@ -108,13 +108,13 @@ where HttpClientBuilder::new().build_with_connector_fn(move |settings, _runtime_components| { let mut builder = Connector::builder() .proxy_config(proxy.clone()) - .tls_provider(tls::Provider::Rustls(PROVIDER.clone())); + .tls_provider(tls::Provider::Rustls(crypto_mode())); builder.set_connector_settings(settings.cloned()); builder.build() }) } else { HttpClientBuilder::new() - .tls_provider(tls::Provider::Rustls(PROVIDER.clone())) + .tls_provider(tls::Provider::Rustls(crypto_mode())) .build_https() }; let eks_config = aws_sdk_eks::config::Builder::from(&config) diff --git a/sources/api/pluto/src/main.rs b/sources/api/pluto/src/main.rs index e97186a91..9266c97de 100644 --- a/sources/api/pluto/src/main.rs +++ b/sources/api/pluto/src/main.rs @@ -57,11 +57,13 @@ const AWS_CONFIG_FILE: &str = "config.pluto"; /// The environment variable that specifies the path to the AWS config file. const AWS_CONFIG_FILE_ENV_VAR: &str = "AWS_CONFIG_FILE"; -// Shared crypto provider for HyperClients -#[cfg(not(feature = "fips"))] -const PROVIDER: CryptoMode = CryptoMode::AwsLc; -#[cfg(feature = "fips")] -const PROVIDER: CryptoMode = CryptoMode::AwsLcFips; +// Shared crypto provider for HyperClients — uses runtime FIPS detection +fn crypto_mode() -> CryptoMode { + CryptoMode::Custom( + bottlerocket_crypto_provider::provider() + .expect("failed to detect FIPS mode for crypto provider"), + ) +} mod error { use crate::{api, ec2, eks}; @@ -537,7 +539,8 @@ async fn run() -> Result<()> { let current_settings = api::get_aws_k8s_info().await.context(error::AwsInfoSnafu)?; let mut aws_k8s_info = SettingsViewDelta::from_api_response(current_settings); - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + bottlerocket_crypto_provider::install_provider() + .expect("failed to install crypto provider"); let temp_dir = tempfile::tempdir().context(error::TempdirSnafu)?; let aws_config_file_path = temp_dir.path().join(AWS_CONFIG_FILE); From aa029da4656dbc5100913d443d447114f766c5ed Mon Sep 17 00:00:00 2001 From: Jingwei Wang Date: Mon, 8 Jun 2026 17:40:07 +0000 Subject: [PATCH 8/8] cfsignal: use bottlerocket-crypto-provider for runtime FIPS detection Replace compile-time FIPS crypto selection with runtime detection via bottlerocket-crypto-provider. The provider reads /proc/sys/crypto/fips_enabled at startup and selects FIPS-restricted or default cipher suites accordingly. The direct aws-lc-rs and rustls dependencies are removed since they are now provided transitively through bottlerocket-crypto-provider. Signed-off-by: Jingwei Wang --- sources/Cargo.lock | 3 +-- sources/cfsignal/Cargo.toml | 5 ++--- sources/cfsignal/src/cloudformation.rs | 11 ++++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 106250fb4..1aa3f9cb0 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -1904,14 +1904,13 @@ name = "cfsignal" version = "0.1.0" dependencies = [ "aws-config", - "aws-lc-rs", "aws-sdk-cloudformation", "aws-smithy-http-client 0.1.0", "aws-types", + "bottlerocket-crypto-provider", "generate-readme", "imdsclient", "log", - "rustls 0.23.42", "serde", "simplelog", "snafu", diff --git a/sources/cfsignal/Cargo.toml b/sources/cfsignal/Cargo.toml index 8e5747a20..9b9ff67cc 100644 --- a/sources/cfsignal/Cargo.toml +++ b/sources/cfsignal/Cargo.toml @@ -8,17 +8,16 @@ publish = false exclude = ["README.md"] [features] -fips = ["aws-lc-rs/fips", "aws-smithy-http-client/rustls-aws-lc-fips"] +fips = ["aws-smithy-http-client/rustls-aws-lc-fips"] [dependencies] aws-config.workspace = true -aws-lc-rs = { workspace = true, features = ["bindgen"] } aws-sdk-cloudformation.workspace = true aws-smithy-http-client = { workspace = true, features = ["rustls-aws-lc"] } aws-types.workspace = true imdsclient.workspace = true log.workspace = true -rustls.workspace = true +bottlerocket-crypto-provider.workspace = true serde = { workspace = true, features = ["derive"] } simplelog.workspace = true snafu.workspace = true diff --git a/sources/cfsignal/src/cloudformation.rs b/sources/cfsignal/src/cloudformation.rs index 484fd3a85..711f61b41 100644 --- a/sources/cfsignal/src/cloudformation.rs +++ b/sources/cfsignal/src/cloudformation.rs @@ -16,7 +16,8 @@ pub async fn signal_resource( status: String, ) -> Result<()> { info!("Connecting to IMDS"); - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + bottlerocket_crypto_provider::install_provider() + .expect("failed to install crypto provider"); let mut client = ImdsClient::new(); let instance_id = get_instance_id(&mut client).await?; let region = get_region(&mut client).await?; @@ -27,10 +28,10 @@ pub async fn signal_resource( .load() .await; - #[cfg(feature = "fips")] - let crypto_mode = CryptoMode::AwsLcFips; - #[cfg(not(feature = "fips"))] - let crypto_mode = CryptoMode::AwsLc; + let crypto_mode = CryptoMode::Custom( + bottlerocket_crypto_provider::provider() + .expect("failed to detect FIPS mode for crypto provider"), + ); let https_proxy: Option = match env::var_os("HTTPS_PROXY") { Some(https_proxy) => https_proxy.to_str().map(|h| h.to_string()),