Skip to content

Commit 2a47bbb

Browse files
authored
feat(NODE-7047)!: use custom credential provider first after URI (#4656)
1 parent fc43e09 commit 2a47bbb

File tree

3 files changed

+179
-49
lines changed

3 files changed

+179
-49
lines changed

src/cmap/auth/mongo_credentials.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -134,26 +134,6 @@ export class MongoCredentials {
134134
this.mechanism = options.mechanism || AuthMechanism.MONGODB_DEFAULT;
135135
this.mechanismProperties = options.mechanismProperties || {};
136136

137-
if (this.mechanism.match(/MONGODB-AWS/i)) {
138-
if (!this.username && process.env.AWS_ACCESS_KEY_ID) {
139-
this.username = process.env.AWS_ACCESS_KEY_ID;
140-
}
141-
142-
if (!this.password && process.env.AWS_SECRET_ACCESS_KEY) {
143-
this.password = process.env.AWS_SECRET_ACCESS_KEY;
144-
}
145-
146-
if (
147-
this.mechanismProperties.AWS_SESSION_TOKEN == null &&
148-
process.env.AWS_SESSION_TOKEN != null
149-
) {
150-
this.mechanismProperties = {
151-
...this.mechanismProperties,
152-
AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN
153-
};
154-
}
155-
}
156-
157137
if (this.mechanism === AuthMechanism.MONGODB_OIDC && !this.mechanismProperties.ALLOWED_HOSTS) {
158138
this.mechanismProperties = {
159139
...this.mechanismProperties,

test/integration/auth/mongodb_aws.test.ts

Lines changed: 122 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -162,39 +162,132 @@ describe('MONGODB-AWS', function () {
162162
});
163163
});
164164

165-
context('when user supplies a credentials provider', function () {
166-
let providerCount = 0;
167-
168-
beforeEach(function () {
169-
// If we have a username the credentials have been set from the URI, options, or environment
170-
// variables per the auth spec stated order.
171-
if (client.options.credentials.username) {
172-
this.skipReason = 'Credentials in the URI on env variables will not use custom provider.';
173-
return this.skip();
174-
}
175-
});
165+
context('when using a custom credential provider', function () {
166+
// NOTE: Logic for scenarios 1-6 is handled via the evergreen variant configs.
167+
// Scenarios 1-6 from the previous section with a user provided AWS_CREDENTIAL_PROVIDER auth mechanism
168+
// property. This credentials MAY be obtained from the default credential provider from the AWS SDK.
169+
// If the default provider does not cover all scenarios above, those not covered MAY be skipped.
170+
// In these tests the driver MUST also assert that the user provided credential provider was called
171+
// in each test. This may be via a custom function or object that wraps the calls to the custom provider
172+
// and asserts that it was called at least once. For test scenarios where the drivers tools scripts put
173+
// the credentials in the MONGODB_URI, drivers MAY extract the credentials from the URI and return the AWS
174+
// credentials directly from the custom provider instead of using the AWS SDK default provider.
175+
context('1. Custom Credential Provider Authenticates', function () {
176+
let providerCount = 0;
176177

177-
it('authenticates with a user provided credentials provider', async function () {
178-
const credentialProvider = AWSSDKCredentialProvider.awsSDK;
179-
const provider = async () => {
180-
providerCount++;
181-
return await credentialProvider.fromNodeProviderChain().apply();
182-
};
183-
client = this.configuration.newClient(process.env.MONGODB_URI, {
184-
authMechanismProperties: {
185-
AWS_CREDENTIAL_PROVIDER: provider
178+
beforeEach(function () {
179+
// If we have a username the credentials have been set from the URI, options, or environment
180+
// variables per the auth spec stated order.
181+
if (client.options.credentials.username) {
182+
this.skipReason = 'Credentials in the URI will not use custom provider.';
183+
return this.skip();
186184
}
187185
});
188186

189-
const result = await client
190-
.db('aws')
191-
.collection('aws_test')
192-
.estimatedDocumentCount()
193-
.catch(error => error);
187+
it('authenticates with a user provided credentials provider', async function () {
188+
const credentialProvider = AWSSDKCredentialProvider.awsSDK;
189+
const provider = async () => {
190+
providerCount++;
191+
return await credentialProvider.fromNodeProviderChain().apply();
192+
};
193+
client = this.configuration.newClient(process.env.MONGODB_URI, {
194+
authMechanismProperties: {
195+
AWS_CREDENTIAL_PROVIDER: provider
196+
}
197+
});
194198

195-
expect(result).to.not.be.instanceOf(MongoServerError);
196-
expect(result).to.be.a('number');
197-
expect(providerCount).to.be.greaterThan(0);
199+
const result = await client
200+
.db('aws')
201+
.collection('aws_test')
202+
.estimatedDocumentCount()
203+
.catch(error => error);
204+
205+
expect(result).to.not.be.instanceOf(MongoServerError);
206+
expect(result).to.be.a('number');
207+
expect(providerCount).to.be.greaterThan(0);
208+
});
209+
});
210+
211+
context('2. Custom Credential Provider Authentication Precedence', function () {
212+
// Create a MongoClient configured with AWS auth and credentials in the URI.
213+
// Example: mongodb://<AccessKeyId>:<SecretAccessKey>@localhost:27017/?authMechanism=MONGODB-AWS
214+
// Configure a custom credential provider to pass valid AWS credentials. The provider must
215+
// track if it was called.
216+
// Expect authentication to succeed and the custom credential provider was not called.
217+
context('Case 1: Credentials in URI Take Precedence', function () {
218+
let providerCount = 0;
219+
let provider;
220+
221+
beforeEach(function () {
222+
if (!client?.options.credentials.username) {
223+
this.skipReason = 'Test only runs when credentials are present in the URI';
224+
return this.skip();
225+
}
226+
const credentialProvider = AWSSDKCredentialProvider.awsSDK;
227+
provider = async () => {
228+
providerCount++;
229+
return await credentialProvider.fromNodeProviderChain().apply();
230+
};
231+
});
232+
233+
it('authenticates with a user provided credentials provider', async function () {
234+
client = this.configuration.newClient(process.env.MONGODB_URI, {
235+
authMechanismProperties: {
236+
AWS_CREDENTIAL_PROVIDER: provider
237+
}
238+
});
239+
240+
const result = await client
241+
.db('aws')
242+
.collection('aws_test')
243+
.estimatedDocumentCount()
244+
.catch(error => error);
245+
246+
expect(result).to.not.be.instanceOf(MongoServerError);
247+
expect(result).to.be.a('number');
248+
expect(providerCount).to.equal(0);
249+
});
250+
});
251+
252+
// Run this test in an environment with AWS credentials configured as environment variables
253+
// (e.g. AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN)
254+
// Create a MongoClient configured to use AWS auth. Example: mongodb://localhost:27017/?authMechanism=MONGODB-AWS.
255+
// Configure a custom credential provider to pass valid AWS credentials. The provider must track if it was called.
256+
// Expect authentication to succeed and the custom credential provider was called.
257+
context('Case 2: Custom Provider Takes Precedence Over Environment Variables', function () {
258+
let providerCount = 0;
259+
let provider;
260+
261+
beforeEach(function () {
262+
if (client?.options.credentials.username || !process.env.AWS_ACCESS_KEY_ID) {
263+
this.skipReason = 'Test only runs when credentials are present in the environment';
264+
return this.skip();
265+
}
266+
const credentialProvider = AWSSDKCredentialProvider.awsSDK;
267+
provider = async () => {
268+
providerCount++;
269+
return await credentialProvider.fromNodeProviderChain().apply();
270+
};
271+
});
272+
273+
it('authenticates with a user provided credentials provider', async function () {
274+
client = this.configuration.newClient(process.env.MONGODB_URI, {
275+
authMechanismProperties: {
276+
AWS_CREDENTIAL_PROVIDER: provider
277+
}
278+
});
279+
280+
const result = await client
281+
.db('aws')
282+
.collection('aws_test')
283+
.estimatedDocumentCount()
284+
.catch(error => error);
285+
286+
expect(result).to.not.be.instanceOf(MongoServerError);
287+
expect(result).to.be.a('number');
288+
expect(providerCount).to.be.greaterThan(0);
289+
});
290+
});
198291
});
199292
});
200293

@@ -217,7 +310,7 @@ describe('MONGODB-AWS', function () {
217310
.catch(error => error);
218311

219312
expect(client).to.have.nested.property('s.authProviders');
220-
const provider = client.s.authProviders.getOrCreateProvider('MONGODB-AWS');
313+
const provider = client.s.authProviders.getOrCreateProvider('MONGODB-AWS', {});
221314
expect(provider).to.be.instanceOf(MongoDBAWS);
222315
});
223316

test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,61 @@ describe('26. Custom AWS Credential Providers', metadata, () => {
101101
});
102102
}
103103
);
104+
105+
// Ensure a valid AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are present in the environment.
106+
// Create a MongoClient named setupClient.
107+
// Create a ClientEncryption object with the following options:
108+
// class ClientEncryptionOpts {
109+
// keyVaultClient: <setupClient>,
110+
// keyVaultNamespace: "keyvault.datakeys",
111+
// kmsProviders: { "aws": {} },
112+
// credentialProviders: { "aws": <object/function that returns valid credentials from the secrets manager> }
113+
// }
114+
// Use the client encryption to create a datakey using the "aws" KMS provider. This should successfully load
115+
// and use the AWS credentials that were provided by the secrets manager for the remote provider. Assert the
116+
// datakey was created and that the custom credential provider was called at least once.
117+
context(
118+
'Case 4: ClientEncryption with credentialProviders and valid environment variables',
119+
metadata,
120+
function () {
121+
let clientEncryption;
122+
let providerCount = 0;
123+
let previousAccessKey;
124+
let previousSecretKey;
125+
126+
beforeEach(function () {
127+
previousAccessKey = process.env.AWS_ACCESS_KEY_ID;
128+
previousSecretKey = process.env.AWS_SECRET_ACCESS_KEY;
129+
process.env.AWS_ACCESS_KEY_ID = process.env.FLE_AWS_KEY;
130+
process.env.AWS_SECRET_ACCESS_KEY = process.env.FLE_AWS_SECRET;
131+
132+
const options = {
133+
keyVaultNamespace: 'keyvault.datakeys',
134+
kmsProviders: { aws: {} },
135+
credentialProviders: {
136+
aws: async () => {
137+
providerCount++;
138+
return {
139+
accessKeyId: process.env.FLE_AWS_KEY,
140+
secretAccessKey: process.env.FLE_AWS_SECRET
141+
};
142+
}
143+
},
144+
extraOptions: getEncryptExtraOptions()
145+
};
146+
clientEncryption = new ClientEncryption(keyVaultClient, options);
147+
});
148+
149+
afterEach(function () {
150+
process.env.AWS_ACCESS_KEY_ID = previousAccessKey;
151+
process.env.AWS_SECRET_ACCESS_KEY = previousSecretKey;
152+
});
153+
154+
it('is successful', metadata, async function () {
155+
const dk = await clientEncryption.createDataKey('aws', { masterKey });
156+
expect(dk).to.be.instanceOf(Binary);
157+
expect(providerCount).to.be.greaterThan(0);
158+
});
159+
}
160+
);
104161
});

0 commit comments

Comments
 (0)