diff --git a/driver/src/client/auth.rs b/driver/src/client/auth.rs index 277517a34..8f641f892 100644 --- a/driver/src/client/auth.rs +++ b/driver/src/client/auth.rs @@ -202,13 +202,25 @@ impl AuthMechanism { } #[cfg(feature = "aws-auth")] AuthMechanism::MongoDbAws => { - if credential.username.is_some() && credential.password.is_none() { - return Err(ErrorKind::InvalidArgument { - message: "Username cannot be provided without password for MONGODB-AWS \ - authentication" - .to_string(), - } - .into()); + if credential.username.is_some() { + return Err(Error::invalid_argument( + "Username cannot be provided for MONGODB-AWS authentication", + )); + } + if credential.password.is_some() { + return Err(Error::invalid_argument( + "Password cannot be provided for MONGODB-AWS authentication", + )); + } + if credential + .mechanism_properties + .as_ref() + .map_or(false, |mp| mp.contains_key("AWS_SESSION_TOKEN")) + { + return Err(Error::invalid_argument( + "AWS_SESSION_TOKEN mechanism property cannot be provided for MONGODB-AWS \ + authentication", + )); } Ok(()) diff --git a/driver/src/client/auth/aws.rs b/driver/src/client/auth/aws.rs index bfe3233e3..4e9d2f8f7 100644 --- a/driver/src/client/auth/aws.rs +++ b/driver/src/client/auth/aws.rs @@ -105,35 +105,18 @@ pub(super) async fn authenticate_stream( } // Find credentials using MongoDB URI or AWS SDK -pub(crate) async fn get_aws_credentials(credential: &Credential) -> Result { - if let (Some(access_key), Some(secret_key)) = (&credential.username, &credential.password) { - // Look for credentials in the MongoDB URI - Ok(Credentials::new( - access_key.clone(), - secret_key.clone(), - credential - .mechanism_properties - .as_ref() - .and_then(|mp| mp.get_str("AWS_SESSION_TOKEN").ok()) - .map(str::to_owned), - None, - "MongoDB URI", - )) - } else { - // If credentials are not provided in the URI, use the AWS SDK to load - let config = aws_config::load_defaults(BehaviorVersion::latest()).await; - let creds = config - .credentials_provider() - .ok_or_else(|| { - Error::authentication_error(MECH_NAME, "no credential provider configured") - })? - .provide_credentials() - .await - .map_err(|e| { - Error::authentication_error(MECH_NAME, &format!("failed to get creds: {e}")) - })?; - Ok(creds) - } +pub(crate) async fn get_aws_credentials(_credential: &Credential) -> Result { + // Use the AWS SDK to load credentials + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let creds = config + .credentials_provider() + .ok_or_else(|| Error::authentication_error(MECH_NAME, "no credential provider configured"))? + .provide_credentials() + .await + .map_err(|e| { + Error::authentication_error(MECH_NAME, &format!("failed to get creds: {e}")) + })?; + Ok(creds) } pub fn compute_aws_sigv4_payload( diff --git a/spec/auth/legacy/connection-string.json b/spec/auth/legacy/connection-string.json index 3a099c813..8982b61d5 100644 --- a/spec/auth/legacy/connection-string.json +++ b/spec/auth/legacy/connection-string.json @@ -440,6 +440,21 @@ } } }, + { + "description": "should throw an exception if username provided (MONGODB-AWS)", + "uri": "mongodb://user@localhost.com/?authMechanism=MONGODB-AWS", + "valid": false + }, + { + "description": "should throw an exception if username and password provided (MONGODB-AWS)", + "uri": "mongodb://user:pass@localhost.com/?authMechanism=MONGODB-AWS", + "valid": false + }, + { + "description": "should throw an exception if AWS_SESSION_TOKEN provided (MONGODB-AWS)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token", + "valid": false + }, { "description": "should recognise the mechanism with test environment (MONGODB-OIDC)", "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test", diff --git a/spec/auth/legacy/connection-string.yml b/spec/auth/legacy/connection-string.yml index 1f5d47004..2b98f0f8f 100644 --- a/spec/auth/legacy/connection-string.yml +++ b/spec/auth/legacy/connection-string.yml @@ -320,6 +320,15 @@ tests: mechanism: MONGODB-AWS mechanism_properties: AWS_SESSION_TOKEN: token!@#$%^&*()_+ +- description: should throw an exception if username provided (MONGODB-AWS) + uri: mongodb://user@localhost.com/?authMechanism=MONGODB-AWS + valid: false +- description: should throw an exception if username and password provided (MONGODB-AWS) + uri: mongodb://user:pass@localhost.com/?authMechanism=MONGODB-AWS + valid: false +- description: should throw an exception if AWS_SESSION_TOKEN provided (MONGODB-AWS) + uri: mongodb://localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token + valid: false - description: should recognise the mechanism with test environment (MONGODB-OIDC) uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test valid: true diff --git a/spec/auth/mongodb-aws.md b/spec/auth/mongodb-aws.md index 6e166d285..74faa3ad8 100644 --- a/spec/auth/mongodb-aws.md +++ b/spec/auth/mongodb-aws.md @@ -2,7 +2,7 @@ Drivers MUST test the following scenarios: -1. `Regular Credentials`: Auth via an `ACCESS_KEY_ID` and `SECRET_ACCESS_KEY` pair +1. `Regular Credentials`: Auth via an `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` pair 2. `EC2 Credentials`: Auth from an EC2 instance via temporary credentials assigned to the machine 3. `ECS Credentials`: Auth from an ECS instance via temporary credentials assigned to the task 4. `Assume Role`: Auth via temporary credentials obtained from an STS AssumeRole request @@ -12,8 +12,7 @@ Drivers MUST test the following scenarios: 7. Caching of AWS credentials fetched by the driver. For brevity, this section gives the values ``, `` and `` in place of a valid access -key ID, secret access key and session token (also known as a security token). Note that if these values are passed into -the URI they MUST be URL encoded. Sample values are below. +key ID, secret access key and session token (also known as a security token). Sample values are below. ```text AccessKeyId=AKIAI44QH8DHBEXAMPLE @@ -21,13 +20,42 @@ SecretAccessKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY Token=AQoDYXdzEJr... ``` +## Testing custom credential providers + +If the driver supports custom AWS credential providers, the driver MUST test the following: + +### 1. Custom Credential Provider Authenticates + +Scenarios 1-6 from the previous section with a user provided `AWS_CREDENTIAL_PROVIDER` auth mechanism property. This +credentials MAY be obtained from the default credential provider from the AWS SDK. If the default provider does not +cover all scenarios above, those not covered MAY be skipped. In these tests the driver MUST also assert that the user +provided credential provider was called in each test. This may be via a custom function or object that wraps the calls +to the custom provider and asserts that it was called at least once. For test scenarios where the drivers tools scripts +put the credentials in the MONGODB_URI, drivers MAY extract the credentials from the URI and return the AWS credentials +directly from the custom provider instead of using the AWS SDK default provider. + +### 2. Custom Credential Provider Authentication Precedence + +#### Case 1: Credentials in URI Take Precedence *Removed* + +#### Case 2: Custom Provider Takes Precedence Over Environment Variables + +Run this test in an environment with AWS credentials configured as environment variables (e.g. `AWS_ACCESS_KEY_ID`, +`AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`) + +Create a `MongoClient` configured to use AWS auth. Example: `mongodb://localhost:27017/?authMechanism=MONGODB-AWS`. + +Configure a custom credential provider to pass valid AWS credentials. The provider must track if it was called. + +Expect authentication to succeed and the custom credential provider was called. + ## Regular credentials -Drivers MUST be able to authenticate by providing a valid access key id and secret access key pair as the username and -password, respectively, in the MongoDB URI. An example of a valid URI would be: +Drivers MUST be able to authenticate when a valid access key id and secret access key pair are present in the +environment. Drivers MUST provide the --nouri option to aws_tester.py in drivers-evergreen-tools for this test. ```text -mongodb://:@localhost/?authMechanism=MONGODB-AWS +mongodb://localhost/?authMechanism=MONGODB-AWS ``` ## EC2 Credentials @@ -59,11 +87,11 @@ mongodb://localhost/?authMechanism=MONGODB-AWS ## AssumeRole Drivers MUST be able to authenticate using temporary credentials returned from an assume role request. These temporary -credentials consist of an access key ID, a secret access key, and a security token passed into the URI. A sample URI -would be: +credentials consist of an access key ID, a secret access key, and a security token present in the environment. Drivers +MUST provide the --nouri option to aws_tester.py in drivers-evergreen-tools for this test. A sample URI would be: ```text -mongodb://:@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN: +mongodb://localhost/?authMechanism=MONGODB-AWS ``` ## Assume Role with Web Identity diff --git a/spec/auth/mongodb-oidc.md b/spec/auth/mongodb-oidc.md index 7afa3d9ad..affb3e6ef 100644 --- a/spec/auth/mongodb-oidc.md +++ b/spec/auth/mongodb-oidc.md @@ -18,17 +18,25 @@ ______________________________________________________________________ ## Machine Authentication Flow Prose Tests -Drivers MUST run the machine prose tests when `OIDC_TOKEN_DIR` is set. Drivers can either set the `ENVIRONMENT:test` -auth mechanism property, or use a custom callback that also reads the file. +Drivers MUST run these tests in all supported OIDC environments: -Drivers can also choose to run the machine prose tests on GCP or Azure VMs, or on the Kubernetes clusters. +- A callback that reads the token file for `ENVIRONMENT:test`. A callback enables testing additional behaviors. Tests + and assertions limited to a callback are noted with `[callback-only]`. +- `ENVIRONMENT:test` +- `ENVIRONMENT:gcp` +- `ENVIRONMENT:azure` +- `ENVIRONMENT:k8s` + +The token file `ENVIRONMENT:test` is located in `OIDC_TOKEN_DIR` set by +[drivers-evergreen-tools](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_oidc/README.md) +scripts. Drivers MUST implement all prose tests in this section. Unless otherwise noted, all `MongoClient` instances MUST be configured with `retryReads=false`. > [!NOTE] -> For test cases that create fail points, drivers MUST either use a unique `appName` or explicitly remove the fail point -> callback to prevent interaction between test cases. +> For test cases that create fail points, drivers MUST use a unique `appName` to prevent interaction with other +> environment processes, and explicitly remove the fail point to prevent interaction between test runs. After setting up your OIDC [environment](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_oidc/README.md), @@ -40,17 +48,17 @@ source the `secrets-export.sh` file and use the associated env variables in your - Create an OIDC configured client. - Perform a `find` operation that succeeds. -- Assert that the callback was called 1 time. +- `[callback-only]` Assert that the callback was called 1 time. - Close the client. **1.2 Callback is called once for multiple connections** - Create an OIDC configured client. - Start 10 threads and run 100 `find` operations in each thread that all succeed. -- Assert that the callback was called 1 time. +- `[callback-only]` Assert that the callback was called 1 time. - Close the client. -### (2) OIDC Callback Validation +### (2) `[callback-only]` OIDC Callback Validation **2.1 Valid Callback Inputs** @@ -91,10 +99,10 @@ source the `secrets-export.sh` file and use the associated env variables in your - Create an OIDC configured client. - Poison the *Client Cache* with an invalid access token. - Perform a `find` operation that succeeds. -- Assert that the callback was called 1 time. +- `[callback-only]` Assert that the callback was called 1 time. - Close the client. -**3.2 Authentication failures without cached tokens return an error** +**3.2 `[callback-only]` Authentication failures without cached tokens return an error** - Create an OIDC configured client with an OIDC callback that always returns invalid access tokens. - Perform a `find` operation that fails. @@ -103,7 +111,7 @@ source the `secrets-export.sh` file and use the associated env variables in your **3.3 Unexpected error code does not clear the cache** -- Create a `MongoClient` with an OIDC callback that returns a valid token. +- Create an OIDC configured client. - Set a fail point for `saslStart` commands of the form: ```javascript @@ -122,9 +130,9 @@ source the `secrets-export.sh` file and use the associated env variables in your ``` - Perform a `find` operation that fails. -- Assert that the callback has been called once. +- `[callback-only]` Assert that the callback has been called once. - Perform a `find` operation that succeeds. -- Assert that the callback has been called once. +- `[callback-only]` Assert that the callback has been called once. - Close the client. ### (4) Reauthentication @@ -150,10 +158,11 @@ source the `secrets-export.sh` file and use the associated env variables in your ``` - Perform a `find` operation that succeeds. -- Assert that the callback was called 2 times (once during the connection handshake, and again during reauthentication). +- `[callback-only]` Assert that the callback was called 2 times (once during the connection handshake, and again during + reauthentication). - Close the client. -#### 4.2 Read Commands Fail If Reauthentication Fails +#### `[callback-only]` 4.2 Read Commands Fail If Reauthentication Fails - Create a `MongoClient` whose OIDC callback returns one good token and then bad tokens after the first call. - Perform a `find` operation that succeeds. @@ -178,7 +187,7 @@ source the `secrets-export.sh` file and use the associated env variables in your - Assert that the callback was called 2 times. - Close the client. -#### 4.3 Write Commands Fail If Reauthentication Fails +#### `[callback-only]` 4.3 Write Commands Fail If Reauthentication Fails - Create a `MongoClient` whose OIDC callback returns one good token and then bad tokens after the first call. - Perform an `insert` operation that succeeds. @@ -199,7 +208,7 @@ source the `secrets-export.sh` file and use the associated env variables in your } ``` -- Perform a `find` operation that fails. +- Perform a `insert` operation that fails. - Assert that the callback was called 2 times. - Close the client. @@ -207,9 +216,10 @@ source the `secrets-export.sh` file and use the associated env variables in your - Create an OIDC configured client. - Populate the *Client Cache* with a valid access token to enforce Speculative Authentication. + - This may be done by authenticating a temporary OIDC configured client and copying the cached token. - Perform an `insert` operation that succeeds. - Assert that the callback was not called. -- Assert there were no `SaslStart` commands executed. +- Assert there were no `saslStart` commands executed. - Set a fail point for `insert` commands of the form: ```javascript @@ -228,8 +238,8 @@ source the `secrets-export.sh` file and use the associated env variables in your ``` - Perform an `insert` operation that succeeds. -- Assert that the callback was called once. -- Assert there were `SaslStart` commands executed. +- `[callback-only]` Assert that the callback was called once. +- Assert there were `saslStart` commands executed. - Close the client. #### 4.5 Reauthentication Succeeds when a Session is involved @@ -254,7 +264,8 @@ source the `secrets-export.sh` file and use the associated env variables in your - Start a new session. - In the started session perform a `find` operation that succeeds. -- Assert that the callback was called 2 times (once during the connection handshake, and again during reauthentication). +- `[callback-only]` Assert that the callback was called 2 times (once during the connection handshake, and again during + reauthentication). - Close the session and the client. ## (5) Azure Tests