From deed682614072b6e6761d621723c826201e2d67f Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 15 Sep 2025 09:52:46 +0100 Subject: [PATCH 01/12] feat(NODE-6988): require aws sdk for aws auth --- src/cmap/auth/aws_temporary_credentials.ts | 46 +--------------------- src/cmap/auth/mongodb_aws.ts | 7 +--- test/integration/auth/mongodb_aws.test.ts | 1 - 3 files changed, 3 insertions(+), 51 deletions(-) diff --git a/src/cmap/auth/aws_temporary_credentials.ts b/src/cmap/auth/aws_temporary_credentials.ts index baa1a64fc81..4184aac710e 100644 --- a/src/cmap/auth/aws_temporary_credentials.ts +++ b/src/cmap/auth/aws_temporary_credentials.ts @@ -1,10 +1,5 @@ import { type AWSCredentials, getAwsCredentialProvider } from '../../deps'; import { MongoAWSError } from '../../error'; -import { request } from '../../utils'; - -const AWS_RELATIVE_URI = 'http://169.254.170.2'; -const AWS_EC2_URI = 'http://169.254.169.254'; -const AWS_EC2_PATH = '/latest/meta-data/iam/security-credentials'; /** * @internal @@ -32,7 +27,7 @@ export type AWSCredentialProvider = () => Promise; export abstract class AWSTemporaryCredentialProvider { abstract getCredentials(): Promise; private static _awsSDK: ReturnType; - protected static get awsSDK() { + static get awsSDK() { AWSTemporaryCredentialProvider._awsSDK ??= getAwsCredentialProvider(); return AWSTemporaryCredentialProvider._awsSDK; } @@ -144,42 +139,3 @@ export class AWSSDKCredentialProvider extends AWSTemporaryCredentialProvider { } } } - -/** - * @internal - * Fetches credentials manually (without the AWS SDK), as outlined in the [Obtaining Credentials](https://github.com/mongodb/specifications/blob/master/source/auth/auth.md#obtaining-credentials) - * section of the Auth spec. - */ -export class LegacyAWSTemporaryCredentialProvider extends AWSTemporaryCredentialProvider { - override async getCredentials(): Promise { - // If the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI - // is set then drivers MUST assume that it was set by an AWS ECS agent - if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) { - return await request( - `${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}` - ); - } - - // Otherwise assume we are on an EC2 instance - - // get a token - const token = await request(`${AWS_EC2_URI}/latest/api/token`, { - method: 'PUT', - json: false, - headers: { 'X-aws-ec2-metadata-token-ttl-seconds': 30 } - }); - - // get role name - const roleName = await request(`${AWS_EC2_URI}/${AWS_EC2_PATH}`, { - json: false, - headers: { 'X-aws-ec2-metadata-token': token } - }); - - // get temp credentials - const creds = await request(`${AWS_EC2_URI}/${AWS_EC2_PATH}/${roleName}`, { - headers: { 'X-aws-ec2-metadata-token': token } - }); - - return creds; - } -} diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index 9cb22c82caa..26cb9e7f568 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -12,8 +12,7 @@ import { type AWSCredentialProvider, AWSSDKCredentialProvider, type AWSTempCredentials, - AWSTemporaryCredentialProvider, - LegacyAWSTemporaryCredentialProvider + type AWSTemporaryCredentialProvider } from './aws_temporary_credentials'; import { MongoCredentials } from './mongo_credentials'; import { AuthMechanism } from './providers'; @@ -41,9 +40,7 @@ export class MongoDBAWS extends AuthProvider { super(); this.credentialProvider = credentialProvider; - this.credentialFetcher = AWSTemporaryCredentialProvider.isAWSSDKInstalled - ? new AWSSDKCredentialProvider(credentialProvider) - : new LegacyAWSTemporaryCredentialProvider(); + this.credentialFetcher = new AWSSDKCredentialProvider(credentialProvider); } override async auth(authContext: AuthContext): Promise { diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index e65b9c60bda..bef4ccc29e4 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -153,7 +153,6 @@ describe('MONGODB-AWS', function () { }); it('authenticates with a user provided credentials provider', async function () { - // @ts-expect-error We intentionally access a protected variable. const credentialProvider = AWSTemporaryCredentialProvider.awsSDK; const provider = async () => { providerCount++; From 24b6f4162e41a6ce002d4f724189dc63465db516 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 17 Sep 2025 14:28:42 +0200 Subject: [PATCH 02/12] test: add error test case --- test/integration/auth/mongodb_aws.test.ts | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index bef4ccc29e4..dce144247cf 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -136,6 +136,30 @@ describe('MONGODB-AWS', function () { }); }); + context('when the AWS SDK is not present', function () { + beforeEach(function () { + if (awsSdkPresent) { + this.skipReason = 'Tests error case when the AWS SDK is not installed.'; + return this.skip(); + } + }); + + describe('when attempting AWS auth', function () { + it('throws an error', async function () { + client = this.configuration.newClient(process.env.MONGODB_URI); // use the URI built by the test environment + + const err = await client + .db('aws') + .collection('aws_test') + .estimatedDocumentCount() + .catch(e => e); + + expect(err).to.be.instanceof(MongoServerError); + expect(err.message).to.match(/credential-providers/); + }); + }); + }); + context('when user supplies a credentials provider', function () { let providerCount = 0; From 330a330ef97862a72c1fb31c11be41bd3e465542 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 17 Sep 2025 14:36:33 +0200 Subject: [PATCH 03/12] refactor: test --- test/integration/auth/mongodb_aws.test.ts | 368 +++++++++++----------- 1 file changed, 188 insertions(+), 180 deletions(-) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index dce144247cf..7a1e161568b 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -18,6 +18,7 @@ import { type MongoDBNamespace, type MongoDBResponseConstructor, MongoMissingCredentialsError, + MongoMissingDependencyError, MongoServerError, setDifference } from '../../mongodb'; @@ -53,89 +54,6 @@ describe('MONGODB-AWS', function () { await client?.close(); }); - it('should authorize when successfully authenticated', async function () { - client = this.configuration.newClient(process.env.MONGODB_URI); // use the URI built by the test environment - - const result = await client - .db('aws') - .collection('aws_test') - .estimatedDocumentCount() - .catch(error => error); - - expect(result).to.not.be.instanceOf(MongoServerError); - expect(result).to.be.a('number'); - }); - - describe('ConversationId', function () { - let commandStub: sinon.SinonStub< - [ - ns: MongoDBNamespace, - command: Document, - options?: CommandOptions, - responseType?: MongoDBResponseConstructor - ], - Promise - >; - - let saslStartResult, saslContinue; - - beforeEach(function () { - // spy on connection.command, filter for saslStart and saslContinue commands - commandStub = sinon.stub(Connection.prototype, 'command').callsFake(async function ( - ns: MongoDBNamespace, - command: Document, - options: CommandOptions, - responseType?: MongoDBResponseConstructor - ) { - if (command.saslContinue != null) { - saslContinue = { ...command }; - } - - const result = await commandStub.wrappedMethod.call( - this, - ns, - command, - options, - responseType - ); - - if (command.saslStart != null) { - // Modify the result of the saslStart to check if the saslContinue uses it - result.conversationId = 999; - saslStartResult = { ...result }; - } - - return result; - }); - }); - - afterEach(function () { - commandStub.restore(); - sinon.restore(); - }); - - it('should use conversationId returned by saslStart in saslContinue', async function () { - client = this.configuration.newClient(process.env.MONGODB_URI); // use the URI built by the test environment - - const err = await client - .db('aws') - .collection('aws_test') - .estimatedDocumentCount() - .catch(e => e); - - // Expecting the saslContinue to fail since we changed the conversationId - expect(err).to.be.instanceof(MongoServerError); - expect(err.message).to.match(/Mismatched conversation id/); - - expect(saslStartResult).to.not.be.undefined; - expect(saslContinue).to.not.be.undefined; - - expect(saslStartResult).to.have.property('conversationId', 999); - - expect(saslContinue).to.have.property('conversationId').equal(saslStartResult.conversationId); - }); - }); - context('when the AWS SDK is not present', function () { beforeEach(function () { if (awsSdkPresent) { @@ -154,39 +72,22 @@ describe('MONGODB-AWS', function () { .estimatedDocumentCount() .catch(e => e); - expect(err).to.be.instanceof(MongoServerError); + expect(err).to.be.instanceof(MongoMissingDependencyError); expect(err.message).to.match(/credential-providers/); }); }); }); - context('when user supplies a credentials provider', function () { - let providerCount = 0; - + context('when the AWS SDK is present', function () { beforeEach(function () { if (!awsSdkPresent) { - this.skipReason = 'only relevant to AssumeRoleWithWebIdentity with SDK installed'; - return this.skip(); - } - // If we have a username the credentials have been set from the URI, options, or environment - // variables per the auth spec stated order. - if (client.options.credentials.username) { - this.skipReason = 'Credentials in the URI on env variables will not use custom provider.'; + this.skipReason = 'Tests cases when the AWS SDK is installed.'; return this.skip(); } }); - it('authenticates with a user provided credentials provider', async function () { - const credentialProvider = AWSTemporaryCredentialProvider.awsSDK; - const provider = async () => { - providerCount++; - return await credentialProvider.fromNodeProviderChain().apply(); - }; - client = this.configuration.newClient(process.env.MONGODB_URI, { - authMechanismProperties: { - AWS_CREDENTIAL_PROVIDER: provider - } - }); + it('should authorize when successfully authenticated', async function () { + client = this.configuration.newClient(process.env.MONGODB_URI); // use the URI built by the test environment const result = await client .db('aws') @@ -196,109 +97,216 @@ describe('MONGODB-AWS', function () { expect(result).to.not.be.instanceOf(MongoServerError); expect(result).to.be.a('number'); - expect(providerCount).to.be.greaterThan(0); }); - }); - it('should allow empty string in authMechanismProperties.AWS_SESSION_TOKEN to override AWS_SESSION_TOKEN environment variable', function () { - client = this.configuration.newClient(this.configuration.url(), { - authMechanismProperties: { AWS_SESSION_TOKEN: '' } + describe('ConversationId', function () { + let commandStub: sinon.SinonStub< + [ + ns: MongoDBNamespace, + command: Document, + options?: CommandOptions, + responseType?: MongoDBResponseConstructor + ], + Promise + >; + + let saslStartResult, saslContinue; + + beforeEach(function () { + // spy on connection.command, filter for saslStart and saslContinue commands + commandStub = sinon.stub(Connection.prototype, 'command').callsFake(async function ( + ns: MongoDBNamespace, + command: Document, + options: CommandOptions, + responseType?: MongoDBResponseConstructor + ) { + if (command.saslContinue != null) { + saslContinue = { ...command }; + } + + const result = await commandStub.wrappedMethod.call( + this, + ns, + command, + options, + responseType + ); + + if (command.saslStart != null) { + // Modify the result of the saslStart to check if the saslContinue uses it + result.conversationId = 999; + saslStartResult = { ...result }; + } + + return result; + }); + }); + + afterEach(function () { + commandStub.restore(); + sinon.restore(); + }); + + it('should use conversationId returned by saslStart in saslContinue', async function () { + client = this.configuration.newClient(process.env.MONGODB_URI); // use the URI built by the test environment + + const err = await client + .db('aws') + .collection('aws_test') + .estimatedDocumentCount() + .catch(e => e); + + // Expecting the saslContinue to fail since we changed the conversationId + expect(err).to.be.instanceof(MongoServerError); + expect(err.message).to.match(/Mismatched conversation id/); + + expect(saslStartResult).to.not.be.undefined; + expect(saslContinue).to.not.be.undefined; + + expect(saslStartResult).to.have.property('conversationId', 999); + + expect(saslContinue) + .to.have.property('conversationId') + .equal(saslStartResult.conversationId); + }); }); - expect(client) - .to.have.nested.property('options.credentials.mechanismProperties.AWS_SESSION_TOKEN') - .that.equals(''); - }); - it('should store a MongoDBAWS provider instance per client', async function () { - client = this.configuration.newClient(process.env.MONGODB_URI); + context('when user supplies a credentials provider', function () { + let providerCount = 0; - await client - .db('aws') - .collection('aws_test') - .estimatedDocumentCount() - .catch(error => error); + beforeEach(function () { + // If we have a username the credentials have been set from the URI, options, or environment + // variables per the auth spec stated order. + if (client.options.credentials.username) { + this.skipReason = 'Credentials in the URI on env variables will not use custom provider.'; + return this.skip(); + } + }); - expect(client).to.have.nested.property('s.authProviders'); - const provider = client.s.authProviders.getOrCreateProvider('MONGODB-AWS'); - expect(provider).to.be.instanceOf(MongoDBAWS); - }); + it('authenticates with a user provided credentials provider', async function () { + const credentialProvider = AWSTemporaryCredentialProvider.awsSDK; + const provider = async () => { + providerCount++; + return await credentialProvider.fromNodeProviderChain().apply(); + }; + client = this.configuration.newClient(process.env.MONGODB_URI, { + authMechanismProperties: { + AWS_CREDENTIAL_PROVIDER: provider + } + }); - describe('with missing aws token', () => { - let awsSessionToken: string | undefined; + const result = await client + .db('aws') + .collection('aws_test') + .estimatedDocumentCount() + .catch(error => error); - beforeEach(() => { - awsSessionToken = process.env.AWS_SESSION_TOKEN; - delete process.env.AWS_SESSION_TOKEN; + expect(result).to.not.be.instanceOf(MongoServerError); + expect(result).to.be.a('number'); + expect(providerCount).to.be.greaterThan(0); + }); }); - afterEach(() => { - if (awsSessionToken != null) { - process.env.AWS_SESSION_TOKEN = awsSessionToken; - } + it('should allow empty string in authMechanismProperties.AWS_SESSION_TOKEN to override AWS_SESSION_TOKEN environment variable', function () { + client = this.configuration.newClient(this.configuration.url(), { + authMechanismProperties: { AWS_SESSION_TOKEN: '' } + }); + expect(client) + .to.have.nested.property('options.credentials.mechanismProperties.AWS_SESSION_TOKEN') + .that.equals(''); }); - it('should not throw an exception when aws token is missing', async function () { + it('should store a MongoDBAWS provider instance per client', async function () { client = this.configuration.newClient(process.env.MONGODB_URI); - const result = await client + await client .db('aws') .collection('aws_test') .estimatedDocumentCount() .catch(error => error); - // We check only for the MongoMissingCredentialsError - // and do check for the MongoServerError as the error or numeric result - // that can be returned depending on different types of environments - // getting credentials from different sources. - expect(result).to.not.be.instanceOf(MongoMissingCredentialsError); + expect(client).to.have.nested.property('s.authProviders'); + const provider = client.s.authProviders.getOrCreateProvider('MONGODB-AWS'); + expect(provider).to.be.instanceOf(MongoDBAWS); }); - }); - describe('EC2 with missing credentials', () => { - let client; + describe('with missing aws token', () => { + let awsSessionToken: string | undefined; - beforeEach(function () { - if (!process.env.IS_EC2) { - this.currentTest.skipReason = 'requires an AWS EC2 environment'; - this.skip(); - } - sinon.stub(http, 'request').callsFake(function (...args) { - // We pass in a legacy object that has the same properties as a URL - // but it is not an instanceof URL. - expect(args[0]).to.be.an('object'); - if (typeof args[0] === 'object') { - args[0].hostname = 'www.example.com'; - args[0].port = '81'; + beforeEach(() => { + awsSessionToken = process.env.AWS_SESSION_TOKEN; + delete process.env.AWS_SESSION_TOKEN; + }); + + afterEach(() => { + if (awsSessionToken != null) { + process.env.AWS_SESSION_TOKEN = awsSessionToken; } - return http.request.wrappedMethod.apply(this, args); }); - }); - afterEach(async () => { - sinon.restore(); - await client?.close(); + it('should not throw an exception when aws token is missing', async function () { + client = this.configuration.newClient(process.env.MONGODB_URI); + + const result = await client + .db('aws') + .collection('aws_test') + .estimatedDocumentCount() + .catch(error => error); + + // We check only for the MongoMissingCredentialsError + // and do check for the MongoServerError as the error or numeric result + // that can be returned depending on different types of environments + // getting credentials from different sources. + expect(result).to.not.be.instanceOf(MongoMissingCredentialsError); + }); }); - it('should respect the default timeout of 10000ms', async function () { - const config = this.configuration; - client = config.newClient(process.env.MONGODB_URI, { authMechanism: 'MONGODB-AWS' }); // use the URI built by the test environment - const startTime = performance.now(); + describe('EC2 with missing credentials', () => { + let client; - const caughtError = await client - .db() - .command({ ping: 1 }) - .catch(error => error); + beforeEach(function () { + if (!process.env.IS_EC2) { + this.currentTest.skipReason = 'requires an AWS EC2 environment'; + this.skip(); + } + sinon.stub(http, 'request').callsFake(function (...args) { + // We pass in a legacy object that has the same properties as a URL + // but it is not an instanceof URL. + expect(args[0]).to.be.an('object'); + if (typeof args[0] === 'object') { + args[0].hostname = 'www.example.com'; + args[0].port = '81'; + } + return http.request.wrappedMethod.apply(this, args); + }); + }); - const endTime = performance.now(); - const timeTaken = endTime - startTime; - expect(caughtError).to.be.instanceOf(MongoAWSError); - expect(caughtError) - .property('message') - .match(/(timed out after)|(Could not load credentials)/); - // Credentials provider from the SDK does not allow to configure the timeout - // and defaults to 2 seconds - so we ensure this timeout happens below 12s - // instead of the 10s-12s range previously. - expect(timeTaken).to.be.below(12000); + afterEach(async () => { + sinon.restore(); + await client?.close(); + }); + + it('should respect the default timeout of 10000ms', async function () { + const config = this.configuration; + client = config.newClient(process.env.MONGODB_URI, { authMechanism: 'MONGODB-AWS' }); // use the URI built by the test environment + const startTime = performance.now(); + + const caughtError = await client + .db() + .command({ ping: 1 }) + .catch(error => error); + + const endTime = performance.now(); + const timeTaken = endTime - startTime; + expect(caughtError).to.be.instanceOf(MongoAWSError); + expect(caughtError) + .property('message') + .match(/(timed out after)|(Could not load credentials)/); + // Credentials provider from the SDK does not allow to configure the timeout + // and defaults to 2 seconds - so we ensure this timeout happens below 12s + // instead of the 10s-12s range previously. + expect(timeTaken).to.be.below(12000); + }); }); }); @@ -477,7 +485,7 @@ describe('AWS KMS Credential Fetching', function () { }); it('fetching AWS KMS credentials throws an error', async function () { const error = await refreshKMSCredentials({ aws: {} }).catch(e => e); - expect(error).to.be.instanceOf(MongoAWSError); + expect(error).to.be.instanceOf(MongoMissingDependencyError); }); }); From 61b56ce52af3a24e24f29f0853308a626541530f Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 17 Sep 2025 14:55:23 +0200 Subject: [PATCH 04/12] test: fixes --- test/integration/auth/mongodb_aws.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 7a1e161568b..5770613e188 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -62,7 +62,10 @@ describe('MONGODB-AWS', function () { } }); - describe('when attempting AWS auth', function () { + // TODO(NODE-7046): Unskip when removing support for AWS credentials in URI. + // The drivers tools scripts put the credentials in the URI currently, this will + // need to change when doing the DRIVERS-3131 work. + describe.skip('when attempting AWS auth', function () { it('throws an error', async function () { client = this.configuration.newClient(process.env.MONGODB_URI); // use the URI built by the test environment @@ -72,7 +75,7 @@ describe('MONGODB-AWS', function () { .estimatedDocumentCount() .catch(e => e); - expect(err).to.be.instanceof(MongoMissingDependencyError); + expect(err).to.be.instanceof(MongoAWSError); expect(err.message).to.match(/credential-providers/); }); }); @@ -485,7 +488,8 @@ describe('AWS KMS Credential Fetching', function () { }); it('fetching AWS KMS credentials throws an error', async function () { const error = await refreshKMSCredentials({ aws: {} }).catch(e => e); - expect(error).to.be.instanceOf(MongoMissingDependencyError); + expect(error).to.be.instanceOf(MongoAWSError); + expect(error.message).to.match(/credential-providers/); }); }); From 15d2c1e880c6d150a43bbb43de4a0d4ef10a1e16 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 17 Sep 2025 14:57:25 +0200 Subject: [PATCH 05/12] fix: lint --- ...e_encryption.prose.26.custom_aws_credential_providers.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts index 59e32946baf..6586528d46f 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts @@ -28,7 +28,6 @@ describe('26. Custom AWS Credential Providers', metadata, () => { this.currentTest?.skipReason && this.skip(); keyVaultClient = this.configuration.newClient(process.env.MONGODB_UR); - // @ts-expect-error We intentionally access a protected variable. credentialProvider = AWSTemporaryCredentialProvider.awsSDK; }); From 8ed4048579ac645b82e4fcf1c069c70ab5cc5ed1 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 17 Sep 2025 15:27:33 +0200 Subject: [PATCH 06/12] fix: lint --- test/integration/auth/mongodb_aws.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 5770613e188..0de467ff8eb 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -18,7 +18,6 @@ import { type MongoDBNamespace, type MongoDBResponseConstructor, MongoMissingCredentialsError, - MongoMissingDependencyError, MongoServerError, setDifference } from '../../mongodb'; From a0934220d82519f9cda1df94a389cd38ea722a42 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 17 Sep 2025 16:33:20 +0200 Subject: [PATCH 07/12] chore: refactor tests --- .evergreen/config.in.yml | 9 -- .evergreen/config.yml | 99 ---------------------- .evergreen/generate_evergreen_tasks.js | 38 ++------- .evergreen/prepare-mongodb-aws-ecs-auth.sh | 2 - .evergreen/setup-mongodb-aws-auth-tests.sh | 3 - test/integration/auth/mongodb_aws.test.ts | 92 ++++++-------------- 6 files changed, 30 insertions(+), 213 deletions(-) diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index bae7dede801..c9eb51a7559 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -422,7 +422,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -440,7 +439,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -458,7 +456,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -477,7 +474,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -495,7 +491,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -513,7 +508,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -532,7 +526,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -549,7 +542,6 @@ functions: params: include_expansions_in_env: - DRIVERS_TOOLS - - MONGODB_AWS_SDK - PROJECT_DIRECTORY - MONGODB_BINARIES - AWS_ACCESS_KEY_ID @@ -597,7 +589,6 @@ functions: - AWS_SESSION_TOKEN env: AWS_CREDENTIAL_TYPE: env-creds - MONGODB_AWS_SDK: "true" working_dir: "src" binary: bash args: diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 7fafbd1d63d..5908cde1380 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -377,7 +377,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -394,7 +393,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -411,7 +409,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -429,7 +426,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -446,7 +442,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -463,7 +458,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -481,7 +475,6 @@ functions: include_expansions_in_env: - MONGODB_URI - DRIVERS_TOOLS - - MONGODB_AWS_SDK - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN @@ -497,7 +490,6 @@ functions: params: include_expansions_in_env: - DRIVERS_TOOLS - - MONGODB_AWS_SDK - PROJECT_DIRECTORY - MONGODB_BINARIES - AWS_ACCESS_KEY_ID @@ -542,7 +534,6 @@ functions: - AWS_SESSION_TOKEN env: AWS_CREDENTIAL_TYPE: env-creds - MONGODB_AWS_SDK: 'true' working_dir: src binary: bash args: @@ -1660,7 +1651,6 @@ tasks: - {key: AUTH, value: auth} - {key: ORCHESTRATION_FILE, value: auth-aws.json} - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'true'} - func: install dependencies - func: bootstrap mongo-orchestration - func: assume secrets manager role @@ -1675,7 +1665,6 @@ tasks: - {key: AUTH, value: auth} - {key: ORCHESTRATION_FILE, value: auth-aws.json} - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'true'} - func: install dependencies - func: bootstrap mongo-orchestration - func: assume secrets manager role @@ -1690,7 +1679,6 @@ tasks: - {key: AUTH, value: auth} - {key: ORCHESTRATION_FILE, value: auth-aws.json} - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'true'} - func: install dependencies - func: bootstrap mongo-orchestration - func: assume secrets manager role @@ -1705,7 +1693,6 @@ tasks: - {key: AUTH, value: auth} - {key: ORCHESTRATION_FILE, value: auth-aws.json} - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'true'} - func: install dependencies - func: bootstrap mongo-orchestration - func: assume secrets manager role @@ -1720,7 +1707,6 @@ tasks: - {key: AUTH, value: auth} - {key: ORCHESTRATION_FILE, value: auth-aws.json} - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'true'} - func: install dependencies - func: bootstrap mongo-orchestration - func: assume secrets manager role @@ -1735,7 +1721,6 @@ tasks: - {key: AUTH, value: auth} - {key: ORCHESTRATION_FILE, value: auth-aws.json} - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'true'} - func: install dependencies - func: bootstrap mongo-orchestration - func: assume secrets manager role @@ -1750,7 +1735,6 @@ tasks: - {key: AUTH, value: auth} - {key: ORCHESTRATION_FILE, value: auth-aws.json} - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'true'} - func: install dependencies - func: bootstrap mongo-orchestration - func: assume secrets manager role @@ -1765,87 +1749,10 @@ tasks: - {key: AUTH, value: auth} - {key: ORCHESTRATION_FILE, value: auth-aws.json} - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'true'} - func: install dependencies - func: bootstrap mongo-orchestration - func: assume secrets manager role - func: run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME set - - name: aws-latest-auth-test-run-aws-auth-test-with-regular-aws-credentials-no-peer-dependencies - commands: - - command: expansions.update - type: setup - params: - updates: - - {key: VERSION, value: latest} - - {key: AUTH, value: auth} - - {key: ORCHESTRATION_FILE, value: auth-aws.json} - - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'false'} - - func: install dependencies - - func: bootstrap mongo-orchestration - - func: assume secrets manager role - - func: run aws auth test with regular aws credentials - - name: aws-latest-auth-test-run-aws-auth-test-with-assume-role-credentials-no-peer-dependencies - commands: - - command: expansions.update - type: setup - params: - updates: - - {key: VERSION, value: latest} - - {key: AUTH, value: auth} - - {key: ORCHESTRATION_FILE, value: auth-aws.json} - - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'false'} - - func: install dependencies - - func: bootstrap mongo-orchestration - - func: assume secrets manager role - - func: run aws auth test with assume role credentials - - name: aws-latest-auth-test-run-aws-auth-test-with-aws-credentials-as-environment-variables-no-peer-dependencies - commands: - - command: expansions.update - type: setup - params: - updates: - - {key: VERSION, value: latest} - - {key: AUTH, value: auth} - - {key: ORCHESTRATION_FILE, value: auth-aws.json} - - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'false'} - - func: install dependencies - - func: bootstrap mongo-orchestration - - func: assume secrets manager role - - func: run aws auth test with aws credentials as environment variables - - name: >- - aws-latest-auth-test-run-aws-auth-test-with-aws-credentials-and-session-token-as-environment-variables-no-peer-dependencies - commands: - - command: expansions.update - type: setup - params: - updates: - - {key: VERSION, value: latest} - - {key: AUTH, value: auth} - - {key: ORCHESTRATION_FILE, value: auth-aws.json} - - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'false'} - - func: install dependencies - - func: bootstrap mongo-orchestration - - func: assume secrets manager role - - func: run aws auth test with aws credentials and session token as environment variables - - name: aws-latest-auth-test-run-aws-ECS-auth-test-no-peer-dependencies - commands: - - command: expansions.update - type: setup - params: - updates: - - {key: VERSION, value: latest} - - {key: AUTH, value: auth} - - {key: ORCHESTRATION_FILE, value: auth-aws.json} - - {key: TOPOLOGY, value: server} - - {key: MONGODB_AWS_SDK, value: 'false'} - - func: install dependencies - - func: bootstrap mongo-orchestration - - func: assume secrets manager role - - func: run aws ECS auth test - name: run-spec-benchmark-tests-node-server tags: - run-spec-benchmark-tests @@ -3567,12 +3474,6 @@ buildvariants: - aws-latest-auth-test-run-aws-ECS-auth-test - aws-latest-auth-test-run-aws-auth-test-AssumeRoleWithWebIdentity-with-AWS_ROLE_SESSION_NAME-unset - aws-latest-auth-test-run-aws-auth-test-AssumeRoleWithWebIdentity-with-AWS_ROLE_SESSION_NAME-set - - aws-latest-auth-test-run-aws-auth-test-with-regular-aws-credentials-no-peer-dependencies - - aws-latest-auth-test-run-aws-auth-test-with-assume-role-credentials-no-peer-dependencies - - aws-latest-auth-test-run-aws-auth-test-with-aws-credentials-as-environment-variables-no-peer-dependencies - - >- - aws-latest-auth-test-run-aws-auth-test-with-aws-credentials-and-session-token-as-environment-variables-no-peer-dependencies - - aws-latest-auth-test-run-aws-ECS-auth-test-no-peer-dependencies - name: ubuntu2204-test-atlas-data-lake display_name: Atlas Data Lake Tests run_on: ubuntu2204-large diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index a26908a8ee2..6341ff12677 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -342,14 +342,8 @@ for (const VERSION of AWS_AUTH_VERSIONS) { { func: 'run aws auth test with aws credentials as environment variables' }, { func: 'run aws auth test with aws credentials and session token as environment variables' }, { func: 'run aws ECS auth test' }, - { - func: 'run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME unset', - onlySdk: true - }, - { - func: 'run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME set', - onlySdk: true - } + { func: 'run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME unset' }, + { func: 'run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME set' } ]; const awsTasks = awsFuncs.map(fn => ({ @@ -359,8 +353,7 @@ for (const VERSION of AWS_AUTH_VERSIONS) { VERSION, AUTH: 'auth', ORCHESTRATION_FILE: 'auth-aws.json', - TOPOLOGY: 'server', - MONGODB_AWS_SDK: 'true' + TOPOLOGY: 'server' }), { func: 'install dependencies' }, { func: 'bootstrap mongo-orchestration' }, @@ -369,29 +362,8 @@ for (const VERSION of AWS_AUTH_VERSIONS) { ] })); - const awsNoPeerDependenciesTasks = awsFuncs - .filter(fn => fn.onlySdk !== true) - .map(fn => ({ - name: `${name(fn.func)}-no-peer-dependencies`, - commands: [ - updateExpansions({ - VERSION: VERSION, - AUTH: 'auth', - ORCHESTRATION_FILE: 'auth-aws.json', - TOPOLOGY: 'server', - MONGODB_AWS_SDK: 'false' - }), - { func: 'install dependencies' }, - { func: 'bootstrap mongo-orchestration' }, - { func: 'assume secrets manager role' }, - { func: fn.func } - ] - })); - - const allAwsTasks = awsTasks.concat(awsNoPeerDependenciesTasks); - - TASKS.push(...allAwsTasks); - AWS_AUTH_TASKS.push(...allAwsTasks.map(t => t.name)); + TASKS.push(...awsTasks); + AWS_AUTH_TASKS.push(...awsTasks.map(t => t.name)); } const BUILD_VARIANTS = []; diff --git a/.evergreen/prepare-mongodb-aws-ecs-auth.sh b/.evergreen/prepare-mongodb-aws-ecs-auth.sh index 2de4215c1a7..e9ef2bb0c7b 100755 --- a/.evergreen/prepare-mongodb-aws-ecs-auth.sh +++ b/.evergreen/prepare-mongodb-aws-ecs-auth.sh @@ -10,8 +10,6 @@ mkdir -p $ECS_SRC_DIR/.evergreen set -ex # write test file -echo "export MONGODB_AWS_SDK=$MONGODB_AWS_SDK" >>$PROJECT_DIRECTORY/.evergreen/run-mongodb-aws-ecs-test.sh -echo "if [ $MONGODB_AWS_SDK = 'false' ]; then rm -rf ./node_modules/@aws-sdk/credential-providers; fi" >>$PROJECT_DIRECTORY/.evergreen/run-mongodb-aws-ecs-test.sh echo "npm run check:aws" >>$PROJECT_DIRECTORY/.evergreen/run-mongodb-aws-ecs-test.sh # copy test file to AWS ecs test directory diff --git a/.evergreen/setup-mongodb-aws-auth-tests.sh b/.evergreen/setup-mongodb-aws-auth-tests.sh index 79ab66e55bc..0d91583d046 100644 --- a/.evergreen/setup-mongodb-aws-auth-tests.sh +++ b/.evergreen/setup-mongodb-aws-auth-tests.sh @@ -8,7 +8,6 @@ set +x if [ -z ${MONGODB_URI+omitted} ]; then echo "MONGODB_URI is unset" && exit 1; fi if [ -z ${DRIVERS_TOOLS+omitted} ]; then echo "DRIVERS_TOOLS is unset" && exit 1; fi if [ -z ${AWS_CREDENTIAL_TYPE+omitted} ]; then echo "AWS_CREDENTIAL_TYPE is unset" && exit 1; fi -if [ -z ${MONGODB_AWS_SDK+omitted} ]; then echo "MONGODB_AWS_SDK is unset" && exit 1; fi bash $DRIVERS_TOOLS/.evergreen/auth_aws/setup-secrets.sh @@ -25,7 +24,5 @@ cd $BEFORE npm install --no-save aws4 -if [ $MONGODB_AWS_SDK = 'false' ]; then rm -rf ./node_modules/@aws-sdk/credential-providers; fi - # revert to show test output set -x diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 0de467ff8eb..9c8786db8d6 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -18,6 +18,7 @@ import { type MongoDBNamespace, type MongoDBResponseConstructor, MongoMissingCredentialsError, + MongoMissingDependencyError, MongoServerError, setDifference } from '../../mongodb'; @@ -25,7 +26,6 @@ import { const isMongoDBAWSAuthEnvironment = (process.env.MONGODB_URI ?? '').includes('MONGODB-AWS'); describe('MONGODB-AWS', function () { - let awsSdkPresent; let client: MongoClient; beforeEach(function () { @@ -33,20 +33,6 @@ describe('MONGODB-AWS', function () { this.currentTest.skipReason = 'requires MONGODB_URI to contain MONGODB-AWS auth mechanism'; return this.skip(); } - - const { MONGODB_AWS_SDK = 'unset' } = process.env; - expect( - ['true', 'false'], - `Always inform the AWS tests if they run with or without the SDK (MONGODB_AWS_SDK=${MONGODB_AWS_SDK})` - ).to.include(MONGODB_AWS_SDK); - - awsSdkPresent = AWSTemporaryCredentialProvider.isAWSSDKInstalled; - expect( - awsSdkPresent, - MONGODB_AWS_SDK === 'true' - ? 'expected aws sdk to be installed' - : 'expected aws sdk to not be installed' - ).to.be[MONGODB_AWS_SDK]; }); afterEach(async () => { @@ -55,39 +41,43 @@ describe('MONGODB-AWS', function () { context('when the AWS SDK is not present', function () { beforeEach(function () { - if (awsSdkPresent) { - this.skipReason = 'Tests error case when the AWS SDK is not installed.'; - return this.skip(); - } + AWSTemporaryCredentialProvider.awsSDK['kModuleError'] = new MongoMissingDependencyError( + 'Missing dependency @aws-sdk/credential-providers', + { + cause: new Error(), + dependencyName: '@aws-sdk/credential-providers' + } + ); }); - // TODO(NODE-7046): Unskip when removing support for AWS credentials in URI. - // The drivers tools scripts put the credentials in the URI currently, this will - // need to change when doing the DRIVERS-3131 work. - describe.skip('when attempting AWS auth', function () { + afterEach(function () { + delete AWSTemporaryCredentialProvider.awsSDK['kModuleError']; + }); + + describe('when attempting AWS auth', function () { it('throws an error', async function () { client = this.configuration.newClient(process.env.MONGODB_URI); // use the URI built by the test environment - const err = await client + const result = await client .db('aws') .collection('aws_test') .estimatedDocumentCount() .catch(e => e); - expect(err).to.be.instanceof(MongoAWSError); - expect(err.message).to.match(/credential-providers/); + // TODO(NODE-7046): Remove branch when removing support for AWS credentials in URI. + // The drivers tools scripts put the credentials in the URI currently for some environments, + // this will need to change when doing the DRIVERS-3131 work. + if (!client.options.credentials.username) { + expect(result).to.be.instanceof(MongoAWSError); + expect(result.message).to.match(/credential-providers/); + } else { + expect(result).to.equal(0); + } }); }); }); context('when the AWS SDK is present', function () { - beforeEach(function () { - if (!awsSdkPresent) { - this.skipReason = 'Tests cases when the AWS SDK is installed.'; - return this.skip(); - } - }); - it('should authorize when successfully authenticated', async function () { client = this.configuration.newClient(process.env.MONGODB_URI); // use the URI built by the test environment @@ -389,10 +379,7 @@ describe('MONGODB-AWS', function () { const envCheck = () => { const { AWS_WEB_IDENTITY_TOKEN_FILE = '' } = process.env; - return ( - AWS_WEB_IDENTITY_TOKEN_FILE.length === 0 || - !AWSTemporaryCredentialProvider.isAWSSDKInstalled - ); + return AWS_WEB_IDENTITY_TOKEN_FILE.length === 0; }; beforeEach(function () { @@ -402,7 +389,6 @@ describe('MONGODB-AWS', function () { return this.skip(); } - // @ts-expect-error We intentionally access a protected variable. credentialProvider = AWSTemporaryCredentialProvider.awsSDK; storedEnv = process.env; @@ -473,35 +459,8 @@ describe('MONGODB-AWS', function () { }); } }); -}); - -describe('AWS KMS Credential Fetching', function () { - context('when the AWS SDK is not installed', function () { - beforeEach(function () { - this.currentTest.skipReason = !isMongoDBAWSAuthEnvironment - ? 'Test must run in an AWS auth testing environment' - : AWSTemporaryCredentialProvider.isAWSSDKInstalled - ? 'This test must run in an environment where the AWS SDK is not installed.' - : undefined; - this.currentTest?.skipReason && this.skip(); - }); - it('fetching AWS KMS credentials throws an error', async function () { - const error = await refreshKMSCredentials({ aws: {} }).catch(e => e); - expect(error).to.be.instanceOf(MongoAWSError); - expect(error.message).to.match(/credential-providers/); - }); - }); - - context('when the AWS SDK is installed', function () { - beforeEach(function () { - this.currentTest.skipReason = !isMongoDBAWSAuthEnvironment - ? 'Test must run in an AWS auth testing environment' - : !AWSTemporaryCredentialProvider.isAWSSDKInstalled - ? 'This test must run in an environment where the AWS SDK is installed.' - : undefined; - this.currentTest?.skipReason && this.skip(); - }); + describe('AWS KMS Credential Fetching', function () { context('when a credential provider is not provided', function () { it('KMS credentials are successfully fetched.', async function () { const { aws } = await refreshKMSCredentials({ aws: {} }); @@ -516,7 +475,6 @@ describe('AWS KMS Credential Fetching', function () { let providerCount = 0; beforeEach(function () { - // @ts-expect-error We intentionally access a protected variable. const provider = AWSTemporaryCredentialProvider.awsSDK; credentialProvider = async () => { providerCount++; From 307b3f86ae756c765b293dce871b2702cacab115 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 18 Sep 2025 15:42:13 +0200 Subject: [PATCH 08/12] chore: comments --- src/cmap/auth/aws_temporary_credentials.ts | 38 +++------ src/cmap/auth/mongodb_aws.ts | 10 +-- test/integration/auth/mongodb_aws.test.ts | 98 ++++++++++++++-------- 3 files changed, 80 insertions(+), 66 deletions(-) diff --git a/src/cmap/auth/aws_temporary_credentials.ts b/src/cmap/auth/aws_temporary_credentials.ts index 4184aac710e..8c1c9ff0eba 100644 --- a/src/cmap/auth/aws_temporary_credentials.ts +++ b/src/cmap/auth/aws_temporary_credentials.ts @@ -19,26 +19,9 @@ export interface AWSTempCredentials { /** @public **/ export type AWSCredentialProvider = () => Promise; -/** - * @internal - * - * Fetches temporary AWS credentials. - */ -export abstract class AWSTemporaryCredentialProvider { - abstract getCredentials(): Promise; - private static _awsSDK: ReturnType; - static get awsSDK() { - AWSTemporaryCredentialProvider._awsSDK ??= getAwsCredentialProvider(); - return AWSTemporaryCredentialProvider._awsSDK; - } - - static get isAWSSDKInstalled(): boolean { - return !('kModuleError' in AWSTemporaryCredentialProvider.awsSDK); - } -} - /** @internal */ -export class AWSSDKCredentialProvider extends AWSTemporaryCredentialProvider { +export class AWSSDKCredentialProvider { + private static _awsSDK: ReturnType; private _provider?: AWSCredentialProvider; /** @@ -46,20 +29,23 @@ export class AWSSDKCredentialProvider extends AWSTemporaryCredentialProvider { * @param credentialsProvider - The credentials provider. */ constructor(credentialsProvider?: AWSCredentialProvider) { - super(); - if (credentialsProvider) { this._provider = credentialsProvider; } } + static get awsSDK() { + AWSSDKCredentialProvider._awsSDK ??= getAwsCredentialProvider(); + return AWSSDKCredentialProvider._awsSDK; + } + /** * The AWS SDK caches credentials automatically and handles refresh when the credentials have expired. * To ensure this occurs, we need to cache the `provider` returned by the AWS sdk and re-use it when fetching credentials. */ private get provider(): () => Promise { - if ('kModuleError' in AWSTemporaryCredentialProvider.awsSDK) { - throw AWSTemporaryCredentialProvider.awsSDK.kModuleError; + if ('kModuleError' in AWSSDKCredentialProvider.awsSDK) { + throw AWSSDKCredentialProvider.awsSDK.kModuleError; } if (this._provider) { return this._provider; @@ -107,15 +93,15 @@ export class AWSSDKCredentialProvider extends AWSTemporaryCredentialProvider { this._provider = awsRegionSettingsExist && useRegionalSts - ? AWSTemporaryCredentialProvider.awsSDK.fromNodeProviderChain({ + ? AWSSDKCredentialProvider.awsSDK.fromNodeProviderChain({ clientConfig: { region: AWS_REGION } }) - : AWSTemporaryCredentialProvider.awsSDK.fromNodeProviderChain(); + : AWSSDKCredentialProvider.awsSDK.fromNodeProviderChain(); return this._provider; } - override async getCredentials(): Promise { + async getCredentials(): Promise { /* * Creates a credential provider that will attempt to find credentials from the * following sources (listed in order of precedence): diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index 26cb9e7f568..d8bb29886da 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -11,8 +11,7 @@ import { type AuthContext, AuthProvider } from './auth_provider'; import { type AWSCredentialProvider, AWSSDKCredentialProvider, - type AWSTempCredentials, - type AWSTemporaryCredentialProvider + type AWSTempCredentials } from './aws_temporary_credentials'; import { MongoCredentials } from './mongo_credentials'; import { AuthMechanism } from './providers'; @@ -33,13 +32,10 @@ interface AWSSaslContinuePayload { } export class MongoDBAWS extends AuthProvider { - private credentialFetcher: AWSTemporaryCredentialProvider; - private credentialProvider?: AWSCredentialProvider; + private credentialFetcher: AWSSDKCredentialProvider; constructor(credentialProvider?: AWSCredentialProvider) { super(); - - this.credentialProvider = credentialProvider; this.credentialFetcher = new AWSSDKCredentialProvider(credentialProvider); } @@ -159,7 +155,7 @@ export class MongoDBAWS extends AuthProvider { async function makeTempCredentials( credentials: MongoCredentials, - awsCredentialFetcher: AWSTemporaryCredentialProvider + awsCredentialFetcher: AWSSDKCredentialProvider ): Promise { function makeMongoCredentialsFromAWSTemp(creds: AWSTempCredentials) { // The AWS session token (creds.Token) may or may not be set. diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 9c8786db8d6..2a0b6863db2 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -8,7 +8,7 @@ import * as sinon from 'sinon'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { refreshKMSCredentials } from '../../../src/client-side-encryption/providers'; import { - AWSTemporaryCredentialProvider, + AWSSDKCredentialProvider, type CommandOptions, Connection, type Document, @@ -41,7 +41,7 @@ describe('MONGODB-AWS', function () { context('when the AWS SDK is not present', function () { beforeEach(function () { - AWSTemporaryCredentialProvider.awsSDK['kModuleError'] = new MongoMissingDependencyError( + AWSSDKCredentialProvider.awsSDK['kModuleError'] = new MongoMissingDependencyError( 'Missing dependency @aws-sdk/credential-providers', { cause: new Error(), @@ -51,7 +51,7 @@ describe('MONGODB-AWS', function () { }); afterEach(function () { - delete AWSTemporaryCredentialProvider.awsSDK['kModuleError']; + delete AWSSDKCredentialProvider.awsSDK['kModuleError']; }); describe('when attempting AWS auth', function () { @@ -176,7 +176,7 @@ describe('MONGODB-AWS', function () { }); it('authenticates with a user provided credentials provider', async function () { - const credentialProvider = AWSTemporaryCredentialProvider.awsSDK; + const credentialProvider = AWSSDKCredentialProvider.awsSDK; const provider = async () => { providerCount++; return await credentialProvider.fromNodeProviderChain().apply(); @@ -389,7 +389,7 @@ describe('MONGODB-AWS', function () { return this.skip(); } - credentialProvider = AWSTemporaryCredentialProvider.awsSDK; + credentialProvider = AWSSDKCredentialProvider.awsSDK; storedEnv = process.env; if (test.env.AWS_STS_REGIONAL_ENDPOINTS === undefined) { @@ -461,46 +461,78 @@ describe('MONGODB-AWS', function () { }); describe('AWS KMS Credential Fetching', function () { - context('when a credential provider is not provided', function () { - it('KMS credentials are successfully fetched.', async function () { - const { aws } = await refreshKMSCredentials({ aws: {} }); + context('when the AWS SDK is not installed', function () { + beforeEach(function () { + AWSSDKCredentialProvider.awsSDK['kModuleError'] = new MongoMissingDependencyError( + 'Missing dependency @aws-sdk/credential-providers', + { + cause: new Error(), + dependencyName: '@aws-sdk/credential-providers' + } + ); + }); - expect(aws).to.have.property('accessKeyId'); - expect(aws).to.have.property('secretAccessKey'); + afterEach(function () { + delete AWSSDKCredentialProvider.awsSDK['kModuleError']; + }); + + it('fetching AWS KMS credentials throws an error', async function () { + const result = await refreshKMSCredentials({ aws: {} }).catch(e => e); + + // TODO(NODE-7046): Remove branch when removing support for AWS credentials in URI. + // The drivers tools scripts put the credentials in the URI currently for some environments, + // this will need to change when doing the DRIVERS-3131 work. + if (!client.options.credentials.username) { + expect(result).to.be.instanceof(MongoAWSError); + expect(result.message).to.match(/credential-providers/); + } else { + expect(result).to.equal(0); + } }); }); - context('when a credential provider is provided', function () { - let credentialProvider; - let providerCount = 0; + context('when the AWS SDK is installed', function () { + context('when a credential provider is not provided', function () { + it('KMS credentials are successfully fetched.', async function () { + const { aws } = await refreshKMSCredentials({ aws: {} }); - beforeEach(function () { - const provider = AWSTemporaryCredentialProvider.awsSDK; - credentialProvider = async () => { - providerCount++; - return await provider.fromNodeProviderChain().apply(); - }; + expect(aws).to.have.property('accessKeyId'); + expect(aws).to.have.property('secretAccessKey'); + }); }); - it('KMS credentials are successfully fetched.', async function () { - const { aws } = await refreshKMSCredentials({ aws: {} }, { aws: credentialProvider }); + context('when a credential provider is provided', function () { + let credentialProvider; + let providerCount = 0; - expect(aws).to.have.property('accessKeyId'); - expect(aws).to.have.property('secretAccessKey'); - expect(providerCount).to.be.greaterThan(0); + beforeEach(function () { + const provider = AWSSDKCredentialProvider.awsSDK; + credentialProvider = async () => { + providerCount++; + return await provider.fromNodeProviderChain().apply(); + }; + }); + + it('KMS credentials are successfully fetched.', async function () { + const { aws } = await refreshKMSCredentials({ aws: {} }, { aws: credentialProvider }); + + expect(aws).to.have.property('accessKeyId'); + expect(aws).to.have.property('secretAccessKey'); + expect(providerCount).to.be.greaterThan(0); + }); }); - }); - it('does not return any extra keys for the `aws` credential provider', async function () { - const { aws } = await refreshKMSCredentials({ aws: {} }); + it('does not return any extra keys for the `aws` credential provider', async function () { + const { aws } = await refreshKMSCredentials({ aws: {} }); - const keys = new Set(Object.keys(aws ?? {})); - const allowedKeys = ['accessKeyId', 'secretAccessKey', 'sessionToken']; + const keys = new Set(Object.keys(aws ?? {})); + const allowedKeys = ['accessKeyId', 'secretAccessKey', 'sessionToken']; - expect( - Array.from(setDifference(keys, allowedKeys)), - 'received an unexpected key in the response refreshing KMS credentials' - ).to.deep.equal([]); + expect( + Array.from(setDifference(keys, allowedKeys)), + 'received an unexpected key in the response refreshing KMS credentials' + ).to.deep.equal([]); + }); }); }); }); From 7304022b97f3a9888058765060b99ffbe6755b9f Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 18 Sep 2025 15:48:10 +0200 Subject: [PATCH 09/12] test: fix --- test/integration/auth/mongodb_aws.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 2a0b6863db2..22ebfa15d8a 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -406,7 +406,7 @@ describe('MONGODB-AWS', function () { numberOfFromNodeProviderChainCalls = 0; // @ts-expect-error We intentionally access a protected variable. - AWSTemporaryCredentialProvider._awsSDK = { + AWSSDKCredentialProvider._awsSDK = { fromNodeProviderChain(...args) { calledArguments = args; numberOfFromNodeProviderChainCalls += 1; @@ -428,7 +428,7 @@ describe('MONGODB-AWS', function () { process.env.AWS_REGION = storedEnv.AWS_REGION; } // @ts-expect-error We intentionally access a protected variable. - AWSTemporaryCredentialProvider._awsSDK = credentialProvider; + AWSSDKCredentialProvider._awsSDK = credentialProvider; calledArguments = []; }); From e9cccad4586b58c8c2fb08e40d00e83bfc2fe4c0 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 18 Sep 2025 15:56:51 +0200 Subject: [PATCH 10/12] fix: tests --- test/integration/auth/mongodb_aws.test.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 22ebfa15d8a..d7153cc1d86 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -479,15 +479,8 @@ describe('MONGODB-AWS', function () { it('fetching AWS KMS credentials throws an error', async function () { const result = await refreshKMSCredentials({ aws: {} }).catch(e => e); - // TODO(NODE-7046): Remove branch when removing support for AWS credentials in URI. - // The drivers tools scripts put the credentials in the URI currently for some environments, - // this will need to change when doing the DRIVERS-3131 work. - if (!client.options.credentials.username) { - expect(result).to.be.instanceof(MongoAWSError); - expect(result.message).to.match(/credential-providers/); - } else { - expect(result).to.equal(0); - } + expect(result).to.be.instanceof(MongoAWSError); + expect(result.message).to.match(/credential-providers/); }); }); From de45b9469620c7ac77791f642e9f9bdbe2c50a89 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 18 Sep 2025 16:38:23 +0200 Subject: [PATCH 11/12] chore: comments --- test/integration/auth/mongodb_aws.test.ts | 3 +-- ...tion.prose.26.custom_aws_credential_providers.test.ts | 9 ++------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index d7153cc1d86..cfac1cacd29 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -30,8 +30,7 @@ describe('MONGODB-AWS', function () { beforeEach(function () { if (!isMongoDBAWSAuthEnvironment) { - this.currentTest.skipReason = 'requires MONGODB_URI to contain MONGODB-AWS auth mechanism'; - return this.skip(); + throw new Error('MONGODB-AWS auth tests can only run in an AWS environment.'); } }); diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts index 6586528d46f..078705d7b4b 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; /* eslint-disable @typescript-eslint/no-restricted-imports */ import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption'; -import { AWSTemporaryCredentialProvider, Binary, MongoClient } from '../../mongodb'; +import { AWSSDKCredentialProvider, Binary, MongoClient } from '../../mongodb'; import { getEncryptExtraOptions } from '../../tools/utils'; const metadata: MongoDBMetadataUI = { @@ -22,13 +22,8 @@ describe('26. Custom AWS Credential Providers', metadata, () => { let credentialProvider; beforeEach(async function () { - this.currentTest.skipReason = !AWSTemporaryCredentialProvider.isAWSSDKInstalled - ? 'This test must run in an environment where the AWS SDK is installed.' - : undefined; - this.currentTest?.skipReason && this.skip(); - keyVaultClient = this.configuration.newClient(process.env.MONGODB_UR); - credentialProvider = AWSTemporaryCredentialProvider.awsSDK; + credentialProvider = AWSSDKCredentialProvider.awsSDK; }); afterEach(async () => { From 5d18bf4d0895806d50ca3497c589a4103fcc349a Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 18 Sep 2025 16:59:06 +0200 Subject: [PATCH 12/12] test: bring back skip --- test/integration/auth/mongodb_aws.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index cfac1cacd29..d7153cc1d86 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -30,7 +30,8 @@ describe('MONGODB-AWS', function () { beforeEach(function () { if (!isMongoDBAWSAuthEnvironment) { - throw new Error('MONGODB-AWS auth tests can only run in an AWS environment.'); + this.currentTest.skipReason = 'requires MONGODB_URI to contain MONGODB-AWS auth mechanism'; + return this.skip(); } });