diff --git a/.evergreen/config.yml b/.evergreen/config.yml index c7a03538c..b7a292421 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -223,7 +223,7 @@ buildvariants: - name: aws-auth display_name: "AWS Authentication" - patchable: false + # patchable: false run_on: - ubuntu2004-small expansions: diff --git a/Cargo.lock b/Cargo.lock index 205cb7205..fe45acbd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,9 +139,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.3" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0baa720ebadea158c5bda642ac444a2af0cdf7bb66b46d1e4533de5d1f449d0" +checksum = "02a18fd934af6ae7ca52410d4548b98eb895aab0f1ea417d168d85db1434a141" dependencies = [ "aws-credential-types", "aws-runtime", @@ -223,9 +223,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.80.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e3ed2a9b828ae7763ddaed41d51724d2661a50c45f845b08967e52f4939cfc" +checksum = "f1e9c3c24e36183e2f698235ed38dcfbbdff1d09b9232dc866c4be3011e0b47e" dependencies = [ "aws-credential-types", "aws-runtime", @@ -279,9 +279,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c4dacf2d38996cf729f55e7a762b30918229917eca115de45dfa8dfb97796c9" +checksum = "43c82ba4cab184ea61f6edaafc1072aad3c2a17dcf4c0fce19ac5694b90d8b5f" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -350,9 +350,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.6" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e107ce0783019dbff59b3a244aa0c114e4a8c9d93498af9162608cd5474e796" +checksum = "660f70d9d8af6876b4c9aa8dcb0dbaf0f89b04ee9a4455bea1b4ba03b15f26f6" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -374,9 +374,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.7" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d52251ed4b9776a3e8487b2a01ac915f73b2da3af8fc1e77e0fce697a550d4" +checksum = "937a49ecf061895fca4a6dd8e864208ed9be7546c0527d04bc07d502ec5fba1c" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -642,9 +642,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.31" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -2059,6 +2059,7 @@ dependencies = [ "async-trait", "aws-config", "aws-credential-types", + "aws-sigv4", "aws-types", "backtrace", "base64 0.13.1", @@ -2082,6 +2083,7 @@ dependencies = [ "hickory-resolver", "hmac", "home", + "http 1.3.1", "lambda_runtime", "log", "macro_magic", @@ -2646,9 +2648,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" dependencies = [ "bitflags 2.9.0", ] @@ -3040,9 +3042,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "indexmap 2.9.0", "itoa", @@ -3144,9 +3146,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -3447,9 +3449,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" dependencies = [ "backtrace", "bytes", @@ -3520,9 +3522,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -4356,9 +4358,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index 1c69c1af2..d1209f277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ dns-resolver = ["dep:hickory-resolver", "dep:hickory-proto"] cert-key-password = ["dep:pem", "dep:pkcs8"] # Enable support for MONGODB-AWS authentication. -aws-auth = ["dep:reqwest", "dep:aws-config", "dep:aws-types", "dep:aws-credential-types"] +aws-auth = ["dep:reqwest", "dep:aws-config", "dep:aws-types", "dep:aws-credential-types", "dep:aws-sigv4", "dep:http"] # Enable support for on-demand Azure KMS credentials. azure-kms = ["dep:reqwest"] @@ -138,6 +138,17 @@ version = "1.2.4" optional = true default-features = false +[dependencies.aws-sigv4] +version = "1.3.3" +optional = true +default-features = false +features = ["sign-http"] + +[dependencies.http] +version = "1.3" +optional = true +default-features = false + [dependencies.bson2] git = "https://github.com/mongodb/bson-rust" branch = "2.15.x" diff --git a/src/client/auth/aws.rs b/src/client/auth/aws.rs index 14b3fdb10..022c2781f 100644 --- a/src/client/auth/aws.rs +++ b/src/client/auth/aws.rs @@ -31,6 +31,15 @@ use aws_config::BehaviorVersion; #[cfg(feature = "aws-auth")] use aws_credential_types::{provider::ProvideCredentials, Credentials}; +#[cfg(feature = "aws-auth")] +use aws_sigv4::{ + http_request::{sign, SignableBody, SignableRequest, SigningSettings}, + sign::v4::SigningParams, +}; + +#[cfg(feature = "aws-auth")] +use http::Request; + const AWS_ECS_IP: &str = "169.254.170.2"; const AWS_EC2_IP: &str = "169.254.169.254"; const AWS_LONG_DATE_FMT: &str = "%Y%m%dT%H%M%SZ"; @@ -117,25 +126,32 @@ async fn authenticate_stream_inner( let creds = get_aws_credentials(credential).await.map_err(|e| { Error::authentication_error(MECH_NAME, &format!("failed to get creds: {e}")) })?; - let aws_credential = AwsCredential::from_sdk_creds(creds); let date = Utc::now(); - let authorization_header = aws_credential.compute_authorization_header( + // Generate authorization header using original implementation without AWS SDK + // let authorization_header = aws_credential.compute_authorization_header( + // date, + // &server_first.sts_host, + // &server_first.server_nonce, + // )?; + + // let mut client_second_payload = doc! { + // "a": authorization_header, + // "d": date.format(AWS_LONG_DATE_FMT).to_string(), + // }; + + // if let Some(security_token) = aws_credential.session_token { + // client_second_payload.insert("t", security_token); + // } + + let client_second_payload = compute_aws_sigv4_payload( + creds, date, &server_first.sts_host, &server_first.server_nonce, )?; - let mut client_second_payload = doc! { - "a": authorization_header, - "d": date.format(AWS_LONG_DATE_FMT).to_string(), - }; - - if let Some(security_token) = aws_credential.session_token { - client_second_payload.insert("t", security_token); - } - let mut client_second_payload_bytes = vec![]; client_second_payload.to_writer(&mut client_second_payload_bytes)?; @@ -197,6 +213,119 @@ pub(crate) async fn get_aws_credentials(credential: &Credential) -> Result, + host: &str, + server_nonce: &[u8], +) -> Result { + let region = if host == "sts.amazonaws.com" { + "us-east-1" + } else { + let parts: Vec<_> = host.split('.').collect(); + parts.get(1).copied().unwrap_or("us-east-1") + }; + + let url = format!("https://{host}"); + let date_str = date.format("%Y%m%dT%H%M%SZ").to_string(); + let body_str = "Action=GetCallerIdentity&Version=2011-06-15"; + let body_bytes = body_str.as_bytes(); + let nonce_b64 = base64::encode(server_nonce); + + // Create the HTTP request + let mut builder = Request::builder() + .method("POST") + .uri(&url) + .header("host", host) + .header("content-type", "application/x-www-form-urlencoded") + .header("content-length", body_bytes.len()) + .header("x-amz-date", &date_str) + .header("x-mongodb-gs2-cb-flag", "n") + .header("x-mongodb-server-nonce", &nonce_b64); + + if let Some(token) = creds.session_token() { + builder = builder.header("x-amz-security-token", token); + } + + let mut request = builder.body(body_str.to_string()).map_err(|e| { + Error::authentication_error(MECH_NAME, &format!("Failed to build request: {e}")) + })?; + + let service = "sts"; + let identity = creds.into(); + + // Set up signing parameters + let signing_settings = SigningSettings::default(); + let signing_params = SigningParams::builder() + .identity(&identity) + .region(region) + .name(service) + .time(date.into()) + .settings(signing_settings) + .build() + .map_err(|e| { + Error::authentication_error(MECH_NAME, &format!("Failed to build signing params: {e}")) + })? + .into(); + let headers: Result> = request + .headers() + .iter() + .map(|(k, v)| { + let v = v.to_str().map_err(|_| { + Error::authentication_error( + MECH_NAME, + "Failed to convert header value to valid UTF-8", + ) + })?; + Ok((k.as_str(), v)) + }) + .collect(); + + let signable_request = SignableRequest::new( + request.method().as_str(), + request.uri().to_string(), + headers?.into_iter(), + SignableBody::Bytes(request.body().as_bytes()), + ) + .map_err(|e| { + Error::authentication_error(MECH_NAME, &format!("Failed to create SignableRequest: {e}")) + })?; + + let (signing_instructions, _signature) = sign(signable_request, &signing_params) + .map_err(|e| Error::authentication_error(MECH_NAME, &format!("Signing failed: {e}")))? + .into_parts(); + signing_instructions.apply_to_request_http1x(&mut request); + + let headers = request.headers(); + let authorization_header = headers + .get("authorization") + .ok_or_else(|| Error::authentication_error(MECH_NAME, "Missing authorization header"))? + .to_str() + .map_err(|e| { + Error::authentication_error(MECH_NAME, &format!("Invalid header value: {e}")) + })?; + + let token_header = headers + .get("x-amz-security-token") + .map(|v| { + v.to_str().map_err(|e| { + Error::authentication_error(MECH_NAME, &format!("Invalid token header: {e}")) + }) + }) + .transpose()?; + + let mut payload = doc! { + "a": authorization_header, + "d": date_str, + }; + + if let Some(token) = token_header { + payload.insert("t", token); + } + + Ok(payload) +} + /// Contains the credentials for MONGODB-AWS authentication. // RUST-1529 note: dead_code tag added to avoid unused warnings on expiration field #[allow(dead_code)]