diff --git a/Cargo.lock b/Cargo.lock index e19b2e33c8..792ccf1650 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,6 +337,48 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "aws-config" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fcc63c9860579e4cb396239570e979376e70aab79e496621748a09913f8b36" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.3.0", + "hex", + "http 1.3.1", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687bc16bc431a8533fe0097c7f0182874767f920989d7260950172ae8e3c4465" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + [[package]] name = "aws-lc-rs" version = "1.12.0" @@ -362,6 +404,324 @@ dependencies = [ "paste", ] +[[package]] +name = "aws-runtime" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c4063282c69991e57faab9e5cb21ae557e59f5b0fb285c196335243df8dc25c" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.3.0", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-dsql" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ddc823b43d745f28eb12aa421af0e95ced5624bdb1f14cb5af7bca32d69cef" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.3.0", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8efec445fb78df585327094fcef4cad895b154b58711e504db7a93c41aa27151" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.3.0", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e49cca619c10e7b002dc8e66928ceed66ab7f56c1a3be86c5437bf2d8d89bba" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.3.0", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7420479eac0a53f776cc8f0d493841ffe58ad9d9783f3947be7265784471b47a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand 2.3.0", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3503af839bd8751d0bdc5a46b9cac93a003a353e635b0c12cf2376b5b53e41ea" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.3.1", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99335bec6cdc50a346fda1437f9fefe33abf8c99060739a546a16457f2862ca9" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aff1159006441d02e57204bf57a1b890ba68bedb6904ffd2873c1c4c11c546b" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.4.9", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.6.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.5", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.26", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tower 0.5.2", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14302f06d1d5b7d333fd819943075b13d27c7700b414f574c3c35859bfb55d5e" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand 2.3.0", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e5d9e3a80a18afa109391fb5ad09c3daf887b516c6fd805a157c6ea7994a57" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40076bd09fadbc12d5e026ae080d0930defa606856186e31d83ccc6a255eeaf3" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a322fec39e4df22777ed3ad8ea868ac2f94cd15e1a55f6ee8d8d6305057689a" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "axum" version = "0.5.17" @@ -374,9 +734,9 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", "itoa", "matchit", "memchr", @@ -388,7 +748,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tower", + "tower 0.4.13", "tower-http", "tower-layer", "tower-service", @@ -403,8 +763,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "mime", "tower-layer", "tower-service", @@ -457,12 +817,28 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.6.0" @@ -639,9 +1015,19 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] [[package]] name = "camino" @@ -1549,6 +1935,44 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -1656,6 +2080,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1663,7 +2098,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", ] @@ -1701,19 +2159,94 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.8", + "socket2 0.5.9", "tokio", "tower-service", "tracing", "want", ] +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.9", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.3.1", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.26", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "libc", + "pin-project-lite", + "socket2 0.5.9", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -2049,9 +2582,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -2434,6 +2967,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parking" version = "2.2.1" @@ -2879,6 +3418,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -2986,6 +3531,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.37.28" @@ -3015,19 +3569,43 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.21" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.1", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework 2.11.1", +] + [[package]] name = "rustls-native-certs" version = "0.8.1" @@ -3040,17 +3618,36 @@ dependencies = [ "security-framework 3.2.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "aws-lc-rs", "ring", @@ -3094,6 +3691,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -3345,9 +3952,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3402,6 +4009,18 @@ dependencies = [ "url", ] +[[package]] +name = "sqlx-aws" +version = "0.8.5" +dependencies = [ + "aws-config", + "aws-sdk-dsql", + "sqlx", + "sqlx-core", + "sqlx-postgres", + "tokio", +] + [[package]] name = "sqlx-cli" version = "0.8.5" @@ -3459,8 +4078,8 @@ dependencies = [ "percent-encoding", "regex", "rust_decimal", - "rustls", - "rustls-native-certs", + "rustls 0.23.26", + "rustls-native-certs 0.8.1", "serde", "serde_json", "sha2", @@ -3505,7 +4124,7 @@ dependencies = [ "thiserror 2.0.11", "time", "tokio", - "tower", + "tower 0.4.13", "tracing", "uuid", "validator", @@ -4111,7 +4730,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.8", + "socket2 0.5.9", "tokio-macros", "windows-sys 0.52.0", ] @@ -4127,6 +4746,26 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.26", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -4138,6 +4777,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.19" @@ -4188,6 +4840,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" version = "0.3.5" @@ -4198,11 +4860,11 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "http-range-header", "pin-project-lite", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] @@ -4357,6 +5019,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -4474,6 +5142,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -4846,6 +5520,12 @@ dependencies = [ "tap", ] +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index 1aef121199..387d5d5f38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ # "sqlx-bench", "sqlx-mysql", "sqlx-postgres", + "sqlx-aws", "sqlx-sqlite", "examples/mysql/todos", "examples/postgres/axum-social-with-tests", diff --git a/sqlx-aws/Cargo.toml b/sqlx-aws/Cargo.toml new file mode 100644 index 0000000000..344d7322fa --- /dev/null +++ b/sqlx-aws/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "sqlx-aws" +documentation = "https://docs.rs/sqlx" +description = "AWS specific helpers for using sqlx." + +version.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +authors.workspace = true + +[features] +default = ["dsql"] +dsql = ["sqlx-postgres", "aws-sdk-dsql"] + +[dependencies] +aws-config = { version = "1.6.2", default-features = false, features = ["behavior-version-latest"] } +aws-sdk-dsql = { version = "1.16.0", default-features = false, optional = true } +sqlx-core = { workspace = true } +sqlx-postgres = { workspace = true, optional = true } +tokio = { workspace = true, features = ["rt", "sync"] } + +[dev-dependencies] +sqlx = { workspace = true, features = ["runtime-tokio"] } +tokio = { workspace = true, features = ["rt", "rt-multi-thread", "sync", "macros"] } +aws-config = { version = "1.6.2", features = ["behavior-version-latest"] } +aws-sdk-dsql = { version = "1.16.0" } + +[lints] +workspace = true + +[[example]] +name = "dsql" +features = ["dsql"] diff --git a/sqlx-aws/examples/dsql.rs b/sqlx-aws/examples/dsql.rs new file mode 100644 index 0000000000..80083957c6 --- /dev/null +++ b/sqlx-aws/examples/dsql.rs @@ -0,0 +1,17 @@ +use aws_sdk_dsql::error::BoxError; +use sqlx_aws::iam::dsql::DsqlIamProvider; +use sqlx_postgres::{PgConnectOptions, PgPoolOptions}; + +#[tokio::main] +async fn main() -> Result<(), BoxError> { + let hostname = std::env::var("DSQL_CLUSTER_ENDPOINT") + .expect("please set DSQL_CLUSTER_ENDPOINT is your environment"); + + let provider = DsqlIamProvider::new(hostname).await?; + let opts = PgConnectOptions::new_without_pgpass() + .password(provider) + .database("postgres"); + let _pool = PgPoolOptions::new().connect_with(opts).await?; + + Ok(()) +} diff --git a/sqlx-aws/src/iam/dsql.rs b/sqlx-aws/src/iam/dsql.rs new file mode 100644 index 0000000000..4946c2544c --- /dev/null +++ b/sqlx-aws/src/iam/dsql.rs @@ -0,0 +1,170 @@ +//! Aurora DSQL requires an IAM token in place of a password. Tokens are +//! generated by the AWS SDK using your AWS credentials. + +use std::{ + borrow::Cow, + fmt, + sync::{Arc, RwLock}, + time::Duration, +}; + +use aws_config::{BehaviorVersion, SdkConfig}; +use aws_sdk_dsql::{ + auth_token::{AuthToken, AuthTokenGenerator, Config}, + error::BoxError, +}; +use sqlx_postgres::PasswordProvider; +use tokio::{task::JoinHandle, time::sleep}; + +/// A builder type to get you build a customized [`DsqlIamProvider`], in case +/// the AWS SDK defaults aren't what you're looking for. +/// +/// If you're happy with the AWS SDK defaults, prefer using +/// [`DsqlIamProvider::new`]. +/// +/// +/// ```ignore +/// use sqlx_aws::iam::dsql::*; +/// +/// let b = DsqlIamProviderBuilder::defaults().await; +/// let my_config = Config::builder().hostname("...").build()?; +/// let provider = b.with_generator_config(my_config).await?; +/// ``` +pub struct DsqlIamProviderBuilder { + cfg: SdkConfig, + is_admin: bool, +} + +impl DsqlIamProviderBuilder { + /// A new builder. The AWS SDK is automatically configured. + pub async fn defaults() -> Self { + let cfg = aws_config::load_defaults(BehaviorVersion::latest()).await; + Self::new_with_sdk_cfg(cfg) + } + + /// A new builder with custom SDK config. + pub fn new_with_sdk_cfg(cfg: SdkConfig) -> Self { + Self { + cfg, + is_admin: false, + } + } + + /// Build a provider with the given [`auth_token::Config`]. + pub async fn with_generator_config(self, config: Config) -> Result { + let DsqlIamProviderBuilder { cfg, is_admin } = self; + + // This default value is hardcoded in the AuthTokenGenerator. There is + // no way to share the value. + let expires_in = config.expires_in().unwrap_or(900); + + // Token generation is fast (because it is a local operation). However, + // there is some coordination involved (such as loading AWS credentials, + // or tokio scheduling). We want to avoid ever having stale tokens, and so schedule refreshes slightly ahead of expiry. + let refresh_interval = Duration::from_secs(if expires_in > 60 { + expires_in - 60 + } else { + expires_in + }); + + let generator = AuthTokenGenerator::new(config); + + // Boostrap: try once. This allows for failing fast for the case where + // things haven't been correctly configured. + let auth_token = match is_admin { + true => generator.db_connect_admin_auth_token(&cfg).await, + false => generator.db_connect_auth_token(&cfg).await, + }?; + + let token = Arc::new(RwLock::new(Ok(auth_token))); + let _token = token.clone(); + + let task = tokio::spawn(async move { + sleep(refresh_interval).await; + + loop { + let res = match is_admin { + true => generator.db_connect_admin_auth_token(&cfg).await, + false => generator.db_connect_auth_token(&cfg).await, + }; + match res { + Ok(auth_token) => { + *_token.write().expect("never poisoned") = Ok(auth_token); + sleep(refresh_interval).await; + } + // XXX: In theory, this should almost never happen, because + // we did a boostrap token generation, which should catch + // nearly all errors. However, it is possible that the + // underlying credential provider has failed in some way. + Err(err) => { + // Refreshes are eager, which means it may be possible + // that we're about to replace perfectly good token with + // an error. It doesn't seem worthwhile to guard against + // that, since tokens are short lived and are likely to + // expire shortly anyways. + *_token.write().expect("never poisoned") = Err(err); + + // sleep an arbitrary amount of time to prevent busy + // loops, but not so long that we don't try again (if + // the underlying error has been resolved). + sleep(Duration::from_secs(1)).await; + } + } + } + }); + + Ok(DsqlIamProvider { token, task }) + } +} + +/// A sqlx [`PasswordProvider`] that automatically manages IAM tokens. +/// +/// ```ignore +/// use sqlx_postgres::PgConnectOptions; +/// use sqlx_aws::iam::dsql::*; +/// +/// let provider = DsqlIamProvider::new("peccy.dsql.us-east-1.on.aws").await?; +/// let opts = PgConnectOptions::new_without_pgpass() +/// .password(provider); +/// ``` +pub struct DsqlIamProvider { + token: Arc>>, + task: JoinHandle<()>, +} + +impl Drop for DsqlIamProvider { + fn drop(&mut self) { + self.task.abort(); + } +} + +impl DsqlIamProvider { + pub async fn new(hostname: impl Into) -> Result { + let builder = DsqlIamProviderBuilder::defaults().await; + let config = Config::builder() + .hostname(hostname) + .build() + .expect("hostname was provided"); + builder.with_generator_config(config).await + } +} + +impl PasswordProvider for DsqlIamProvider { + fn password<'a>(&'a self) -> Result, sqlx_core::error::BoxDynError> { + match &*self.token.read().expect("never poisoned") { + Ok(auth_token) => Ok(Cow::Owned(auth_token.as_str().to_string())), + Err(err) => Err(Box::new(RefreshError(format!("{err}")))), + } + } +} + +#[derive(Debug)] +pub struct RefreshError(String); + +impl fmt::Display for RefreshError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "unable to refresh auth token: {}", self.0) + } +} + +impl std::error::Error for RefreshError {} diff --git a/sqlx-aws/src/iam/mod.rs b/sqlx-aws/src/iam/mod.rs new file mode 100644 index 0000000000..a8c2e08796 --- /dev/null +++ b/sqlx-aws/src/iam/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "dsql")] +pub mod dsql; diff --git a/sqlx-aws/src/lib.rs b/sqlx-aws/src/lib.rs new file mode 100644 index 0000000000..3d15d97cc9 --- /dev/null +++ b/sqlx-aws/src/lib.rs @@ -0,0 +1 @@ +pub mod iam; diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index 9ad5eff464..db0bf4bcd3 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -52,6 +52,10 @@ pub enum Error { #[error("error occurred while attempting to establish a TLS connection: {0}")] Tls(#[source] BoxDynError), + /// Password provider failed. + #[error("unable to provide a password: {0}")] + PasswordProvider(#[source] BoxDynError), + /// Unexpected or invalid data encountered while communicating with the database. /// /// This should indicate there is a programming error in a SQLx driver or there diff --git a/sqlx-postgres/src/connection/establish.rs b/sqlx-postgres/src/connection/establish.rs index 1bc4172fbd..9d1a54b8d6 100644 --- a/sqlx-postgres/src/connection/establish.rs +++ b/sqlx-postgres/src/connection/establish.rs @@ -76,9 +76,7 @@ impl PgConnection { // password in clear-text form. stream - .send(Password::Cleartext( - options.password.as_deref().unwrap_or_default(), - )) + .send(Password::Cleartext(options.password.get()?.as_ref())) .await?; } @@ -91,7 +89,7 @@ impl PgConnection { stream .send(Password::Md5 { username: &options.username, - password: options.password.as_deref().unwrap_or_default(), + password: options.password.get()?.as_ref(), salt: body.salt, }) .await?; diff --git a/sqlx-postgres/src/connection/sasl.rs b/sqlx-postgres/src/connection/sasl.rs index 729cc1fcc5..ea638765c0 100644 --- a/sqlx-postgres/src/connection/sasl.rs +++ b/sqlx-postgres/src/connection/sasl.rs @@ -87,7 +87,7 @@ pub(crate) async fn authenticate( // SaltedPassword := Hi(Normalize(password), salt, i) let salted_password = hi( - options.password.as_deref().unwrap_or_default(), + options.password.get()?.as_ref(), &cont.salt, cont.iterations, )?; diff --git a/sqlx-postgres/src/lib.rs b/sqlx-postgres/src/lib.rs index bded75491c..df98ff2db4 100644 --- a/sqlx-postgres/src/lib.rs +++ b/sqlx-postgres/src/lib.rs @@ -54,7 +54,7 @@ pub use database::Postgres; pub use error::{PgDatabaseError, PgErrorPosition}; pub use listener::{PgListener, PgNotification}; pub use message::PgSeverity; -pub use options::{PgConnectOptions, PgSslMode}; +pub use options::{PasswordProvider, PgConnectOptions, PgSslMode}; pub use query_result::PgQueryResult; pub use row::PgRow; pub use statement::PgStatement; diff --git a/sqlx-postgres/src/options/mod.rs b/sqlx-postgres/src/options/mod.rs index 723721a97c..3b8efb654c 100644 --- a/sqlx-postgres/src/options/mod.rs +++ b/sqlx-postgres/src/options/mod.rs @@ -1,8 +1,11 @@ use std::borrow::Cow; use std::env::var; -use std::fmt::{Display, Write}; +use std::fmt::{self, Display, Write}; use std::path::{Path, PathBuf}; +use std::sync::Arc; +use sqlx_core::error::BoxDynError; +use sqlx_core::Error; pub use ssl_mode::PgSslMode; use crate::{connection::LogSettings, net::tls::CertificateInput}; @@ -12,6 +15,91 @@ mod parse; mod pgpass; mod ssl_mode; +/// All the ways to provide a password. +#[derive(Clone)] +pub enum PasswordOption { + /// There is no password. + None, + /// The password is just a value. + // XXX: It is equally valid to provide a password via the next variant + // (`Provider`). This variant avoids allocations without using more memory + // (see the size test). + Value(String), + /// The password is dynamically loaded. + Provider(Arc), +} + +// XXX: Never log the password. +impl fmt::Debug for PasswordOption { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "None"), + Self::Value(_) => f.debug_tuple("Value").finish(), + Self::Provider(_) => f.debug_tuple("Provider").finish(), + } + } +} + +impl PasswordOption { + /// True when [`PasswordOption::None`]. + pub fn is_none(&self) -> bool { + matches!(self, PasswordOption::None) + } + + /// Returns the password, or asks the provider. + pub fn get(&self) -> Result, Error> { + Ok(match self { + PasswordOption::None => Default::default(), + PasswordOption::Value(password) => Cow::Borrowed(&password[..]), + PasswordOption::Provider(provider) => provider + .password() + .map_err(Error::PasswordProvider)?, + }) + } +} + +impl From<&str> for PasswordOption { + fn from(value: &str) -> Self { + PasswordOption::Value(value.into()) + } +} + +impl From<&Cow<'_, str>> for PasswordOption { + fn from(value: &Cow<'_, str>) -> Self { + PasswordOption::Value(value.as_ref().to_string()) + } +} + +impl From> for PasswordOption { + fn from(value: Option) -> Self { + match value { + Some(password) => PasswordOption::Value(password), + None => PasswordOption::None, + } + } +} + +impl

From

for PasswordOption +where + P: PasswordProvider + Send + Sync + 'static, +{ + fn from(value: P) -> Self { + PasswordOption::Provider(Arc::new(value)) + } +} + +/// Supports dynamic password retrieval. +/// +/// Take note that providers are synchronous. This is an intentional design +/// choice, because it discourages "lazy" (just in time) credential fetching +/// which may be put on the connection path. +/// +/// If you need to implement password retrieval asynchronously, consider +/// spawning a task in your provider. +pub trait PasswordProvider { + fn password(&self) -> Result, BoxDynError>; +} + #[doc = include_str!("doc.md")] #[derive(Debug, Clone)] pub struct PgConnectOptions { @@ -19,7 +107,7 @@ pub struct PgConnectOptions { pub(crate) port: u16, pub(crate) socket: Option, pub(crate) username: String, - pub(crate) password: Option, + pub(crate) password: PasswordOption, pub(crate) database: Option, pub(crate) ssl_mode: PgSslMode, pub(crate) ssl_root_cert: Option, @@ -73,7 +161,7 @@ impl PgConnectOptions { host, socket: None, username, - password: var("PGPASSWORD").ok(), + password: var("PGPASSWORD").ok().into(), database, ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from), ssl_client_cert: var("PGSSLCERT").ok().map(CertificateInput::from), @@ -100,7 +188,8 @@ impl PgConnectOptions { self.port, &self.username, self.database.as_deref(), - ); + ) + .into(); } self @@ -179,8 +268,8 @@ impl PgConnectOptions { /// .username("root") /// .password("safe-and-secure"); /// ``` - pub fn password(mut self, password: &str) -> Self { - self.password = Some(password.to_owned()); + pub fn password(mut self, password: impl Into) -> Self { + self.password = password.into(); self } @@ -590,20 +679,72 @@ fn default_host(port: u16) -> String { "localhost".to_owned() } -#[test] -fn test_options_formatting() { - let options = PgConnectOptions::new().options([("geqo", "off")]); - assert_eq!(options.options, Some("-c geqo=off".to_string())); - let options = options.options([("search_path", "sqlx")]); - assert_eq!( - options.options, - Some("-c geqo=off -c search_path=sqlx".to_string()) - ); - let options = PgConnectOptions::new().options([("geqo", "off"), ("statement_timeout", "5min")]); - assert_eq!( - options.options, - Some("-c geqo=off -c statement_timeout=5min".to_string()) - ); - let options = PgConnectOptions::new(); - assert_eq!(options.options, None); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_options_formatting() { + let options = PgConnectOptions::new().options([("geqo", "off")]); + assert_eq!(options.options, Some("-c geqo=off".to_string())); + let options = options.options([("search_path", "sqlx")]); + assert_eq!( + options.options, + Some("-c geqo=off -c search_path=sqlx".to_string()) + ); + let options = + PgConnectOptions::new().options([("geqo", "off"), ("statement_timeout", "5min")]); + assert_eq!( + options.options, + Some("-c geqo=off -c statement_timeout=5min".to_string()) + ); + let options = PgConnectOptions::new(); + assert_eq!(options.options, None); + } + + // [`PgConnectOptions::password`] used to be `Option`. This test + // shows how supporting dynamic passwords doesn't consume more memory for + // users who just want to use static passwords. + #[test] + fn size_of_password_option() { + assert_eq!( + std::mem::size_of::>(), + std::mem::size_of::() + ); + } + + // XXX: Nested to provide a scoped impl of providers. + mod password_providers { + use super::*; + + impl PasswordProvider for String { + fn password<'a>(&'a self) -> Result, BoxDynError> { + Ok(Cow::Borrowed(&self[..])) + } + } + + // This test shows how the [`PasswordProvider`] trait can be used to return + // owned or borrowed values. It serves both as a usage example as well as a + // way to ensure the lifetimes are right. + #[test] + fn password_provider_examples() { + use password_providers::*; + + struct Holder(String); + + impl PasswordProvider for Holder { + fn password<'a>(&'a self) -> Result, BoxDynError> { + Ok(Cow::Owned(self.0.clone())) + } + } + + fn check(provider: impl PasswordProvider) { + let result = provider.password().unwrap(); + assert_eq!("password", result); + } + + check("password".to_string()); + check(Holder("password".to_string())); + } + } } diff --git a/sqlx-postgres/src/options/parse.rs b/sqlx-postgres/src/options/parse.rs index efbf85d8f6..b7fa182886 100644 --- a/sqlx-postgres/src/options/parse.rs +++ b/sqlx-postgres/src/options/parse.rs @@ -127,7 +127,20 @@ impl PgConnectOptions { )) .expect("BUG: generated un-parseable URL"); - if let Some(password) = &self.password { + let password = match self.password.get() { + Ok(password) => Some(password), + Err(err) => { + // XXX: This method is infallible. To avoid a backwards breaking + // change, we log a warning and then proceed with no password. + // The assumption is any use of this Url will result in a failed + // connection (which is an error _somewhere_) and this log line + // can be used as a breadcrumb. + tracing::warn!(%err, "unable to provide a password"); + None + } + }; + + if let Some(password) = &password { let password = utf8_percent_encode(password, NON_ALPHANUMERIC).to_string(); let _ = url.set_password(Some(&password)); } @@ -234,12 +247,13 @@ fn it_parses_user_correctly_from_parameter() { } #[test] -fn it_parses_password_correctly_from_parameter() { +fn it_parses_password_correctly_from_parameter() -> Result<(), Error> { let url = "postgres:///?password=some_pass"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(None, opts.socket); - assert_eq!(Some("some_pass"), opts.password.as_deref()); + assert_eq!("some_pass", opts.password.get()?.as_ref()); + Ok(()) } #[test] @@ -259,11 +273,12 @@ fn it_parses_username_with_at_sign_correctly() { } #[test] -fn it_parses_password_with_non_ascii_chars_correctly() { +fn it_parses_password_with_non_ascii_chars_correctly() -> Result<(), Error> { let url = "postgres://username:p@ssw0rd@hostname:5432/database"; let opts = PgConnectOptions::from_str(url).unwrap(); - assert_eq!(Some("p@ssw0rd".into()), opts.password); + assert_eq!("p@ssw0rd", opts.password.get()?.as_ref()); + Ok(()) } #[test]