From b9748675c55bf137192b51341cade355ef0f862c Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Fri, 17 Jan 2025 16:22:47 +0100 Subject: [PATCH 01/25] feat(NODE-6161): allow custom aws sdk config --- src/client-side-encryption/auto_encrypter.ts | 9 ++- .../client_encryption.ts | 8 ++- src/client-side-encryption/providers/aws.ts | 12 +++- src/client-side-encryption/providers/index.ts | 8 ++- src/cmap/auth/aws_temporary_credentials.ts | 18 +++++- src/cmap/auth/mongo_credentials.ts | 3 + src/cmap/auth/mongodb_aws.ts | 5 +- src/deps.ts | 2 +- src/index.ts | 3 +- src/mongo_client_auth_providers.ts | 10 +++- test/integration/auth/mongodb_aws.test.ts | 58 +++++++++++++++++-- 11 files changed, 118 insertions(+), 18 deletions(-) diff --git a/src/client-side-encryption/auto_encrypter.ts b/src/client-side-encryption/auto_encrypter.ts index a24f8cd6da6..1e63a5bedbf 100644 --- a/src/client-side-encryption/auto_encrypter.ts +++ b/src/client-side-encryption/auto_encrypter.ts @@ -6,6 +6,7 @@ import { import * as net from 'net'; import { deserialize, type Document, serialize } from '../bson'; +import { type AWSCredentialProvider } from '../cmap/auth/aws_temporary_credentials'; import { type CommandOptions, type ProxyOptions } from '../cmap/connection'; import { kDecorateResult } from '../constants'; import { getMongoDBClientEncryption } from '../deps'; @@ -153,6 +154,7 @@ export class AutoEncrypter { _kmsProviders: KMSProviders; _bypassMongocryptdAndCryptShared: boolean; _contextCounter: number; + _awsCredentialProvider?: AWSCredentialProvider; _mongocryptdManager?: MongocryptdManager; _mongocryptdClient?: MongoClient; @@ -328,6 +330,11 @@ export class AutoEncrypter { * This function is a no-op when bypassSpawn is set or the crypt shared library is used. */ async init(): Promise { + // This is handled during init() as the auto encrypter is instantiated during the client's + // parseOptions() call, so the client doesn't have its options set at that point. + this._awsCredentialProvider = + this._client.options.credentials?.mechanismProperties.AWS_CREDENTIAL_PROVIDER; + if (this._bypassMongocryptdAndCryptShared || this.cryptSharedLibVersionInfo) { return; } @@ -439,7 +446,7 @@ export class AutoEncrypter { * the original ones. */ async askForKMSCredentials(): Promise { - return await refreshKMSCredentials(this._kmsProviders); + return await refreshKMSCredentials(this._kmsProviders, this._awsCredentialProvider); } /** diff --git a/src/client-side-encryption/client_encryption.ts b/src/client-side-encryption/client_encryption.ts index 487969cf4de..d6c7115cb6c 100644 --- a/src/client-side-encryption/client_encryption.ts +++ b/src/client-side-encryption/client_encryption.ts @@ -15,6 +15,7 @@ import { type UUID } from '../bson'; import { type AnyBulkWriteOperation, type BulkWriteResult } from '../bulk/common'; +import { type AWSCredentialProvider } from '../cmap/auth/aws_temporary_credentials'; import { type ProxyOptions } from '../cmap/connection'; import { type Collection } from '../collection'; import { type FindCursor } from '../cursor/find_cursor'; @@ -81,6 +82,9 @@ export class ClientEncryption { /** @internal */ _mongoCrypt: MongoCrypt; + /** @internal */ + _awsCredentialProvider?: AWSCredentialProvider; + /** @internal */ static getMongoCrypt(): MongoCryptConstructor { const encryption = getMongoDBClientEncryption(); @@ -125,6 +129,8 @@ export class ClientEncryption { this._kmsProviders = options.kmsProviders || {}; const { timeoutMS } = resolveTimeoutOptions(client, options); this._timeoutMS = timeoutMS; + this._awsCredentialProvider = + client.options.credentials?.mechanismProperties.AWS_CREDENTIAL_PROVIDER; if (options.keyVaultNamespace == null) { throw new MongoCryptInvalidArgumentError('Missing required option `keyVaultNamespace`'); @@ -712,7 +718,7 @@ export class ClientEncryption { * the original ones. */ async askForKMSCredentials(): Promise { - return await refreshKMSCredentials(this._kmsProviders); + return await refreshKMSCredentials(this._kmsProviders, this._awsCredentialProvider); } static get libmongocryptVersion() { diff --git a/src/client-side-encryption/providers/aws.ts b/src/client-side-encryption/providers/aws.ts index 240e560bd9a..e50bb4d232f 100644 --- a/src/client-side-encryption/providers/aws.ts +++ b/src/client-side-encryption/providers/aws.ts @@ -1,11 +1,17 @@ -import { AWSSDKCredentialProvider } from '../../cmap/auth/aws_temporary_credentials'; +import { + type AWSCredentialProvider, + AWSSDKCredentialProvider +} from '../../cmap/auth/aws_temporary_credentials'; import { type KMSProviders } from '.'; /** * @internal */ -export async function loadAWSCredentials(kmsProviders: KMSProviders): Promise { - const credentialProvider = new AWSSDKCredentialProvider(); +export async function loadAWSCredentials( + kmsProviders: KMSProviders, + provider?: AWSCredentialProvider +): Promise { + const credentialProvider = new AWSSDKCredentialProvider(provider); // We shouldn't ever receive a response from the AWS SDK that doesn't have a `SecretAccessKey` // or `AccessKeyId`. However, TS says these fields are optional. We provide empty strings diff --git a/src/client-side-encryption/providers/index.ts b/src/client-side-encryption/providers/index.ts index f254cf69f92..b6590b16425 100644 --- a/src/client-side-encryption/providers/index.ts +++ b/src/client-side-encryption/providers/index.ts @@ -1,4 +1,5 @@ import type { Binary } from '../../bson'; +import { type AWSCredentialProvider } from '../../cmap/auth/aws_temporary_credentials'; import { loadAWSCredentials } from './aws'; import { loadAzureCredentials } from './azure'; import { loadGCPCredentials } from './gcp'; @@ -176,11 +177,14 @@ export function isEmptyCredentials( * * @internal */ -export async function refreshKMSCredentials(kmsProviders: KMSProviders): Promise { +export async function refreshKMSCredentials( + kmsProviders: KMSProviders, + awsProvider?: AWSCredentialProvider +): Promise { let finalKMSProviders = kmsProviders; if (isEmptyCredentials('aws', kmsProviders)) { - finalKMSProviders = await loadAWSCredentials(finalKMSProviders); + finalKMSProviders = await loadAWSCredentials(finalKMSProviders, awsProvider); } if (isEmptyCredentials('gcp', kmsProviders)) { diff --git a/src/cmap/auth/aws_temporary_credentials.ts b/src/cmap/auth/aws_temporary_credentials.ts index c93456a5453..baa1a64fc81 100644 --- a/src/cmap/auth/aws_temporary_credentials.ts +++ b/src/cmap/auth/aws_temporary_credentials.ts @@ -21,6 +21,9 @@ export interface AWSTempCredentials { Expiration?: Date; } +/** @public **/ +export type AWSCredentialProvider = () => Promise; + /** * @internal * @@ -41,7 +44,20 @@ export abstract class AWSTemporaryCredentialProvider { /** @internal */ export class AWSSDKCredentialProvider extends AWSTemporaryCredentialProvider { - private _provider?: () => Promise; + private _provider?: AWSCredentialProvider; + + /** + * Create the SDK credentials provider. + * @param credentialsProvider - The credentials provider. + */ + constructor(credentialsProvider?: AWSCredentialProvider) { + super(); + + if (credentialsProvider) { + this._provider = credentialsProvider; + } + } + /** * 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. diff --git a/src/cmap/auth/mongo_credentials.ts b/src/cmap/auth/mongo_credentials.ts index 97c457945df..0282d311aa8 100644 --- a/src/cmap/auth/mongo_credentials.ts +++ b/src/cmap/auth/mongo_credentials.ts @@ -6,6 +6,7 @@ import { MongoInvalidArgumentError, MongoMissingCredentialsError } from '../../error'; +import type { AWSCredentialProvider } from './aws_temporary_credentials'; import { GSSAPICanonicalizationValue } from './gssapi'; import type { OIDCCallbackFunction } from './mongodb_oidc'; import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './providers'; @@ -68,6 +69,8 @@ export interface AuthMechanismProperties extends Document { ALLOWED_HOSTS?: string[]; /** The resource token for OIDC auth in Azure and GCP. */ TOKEN_RESOURCE?: string; + /** A custom AWS credential provider to use. */ + AWS_CREDENTIAL_PROVIDER?: AWSCredentialProvider; } /** @public */ diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index d9071496b54..34ec76b742e 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -9,6 +9,7 @@ import { import { ByteUtils, maxWireVersion, ns, randomBytes } from '../../utils'; import { type AuthContext, AuthProvider } from './auth_provider'; import { + type AWSCredentialProvider, AWSSDKCredentialProvider, type AWSTempCredentials, AWSTemporaryCredentialProvider, @@ -34,11 +35,11 @@ interface AWSSaslContinuePayload { export class MongoDBAWS extends AuthProvider { private credentialFetcher: AWSTemporaryCredentialProvider; - constructor() { + constructor(credentialProvider?: AWSCredentialProvider) { super(); this.credentialFetcher = AWSTemporaryCredentialProvider.isAWSSDKInstalled - ? new AWSSDKCredentialProvider() + ? new AWSSDKCredentialProvider(credentialProvider) : new LegacyAWSTemporaryCredentialProvider(); } diff --git a/src/deps.ts b/src/deps.ts index 0947ca6efc5..f3064424308 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -78,7 +78,7 @@ export function getZstdLibrary(): ZStandardLib | { kModuleError: MongoMissingDep } /** - * @internal + * @public * Copy of the AwsCredentialIdentityProvider interface from [`smithy/types`](https://socket.dev/npm/package/\@smithy/types/files/1.1.1/dist-types/identity/awsCredentialIdentity.d.ts), * the return type of the aws-sdk's `fromNodeProviderChain().provider()`. */ diff --git a/src/index.ts b/src/index.ts index dfc8ac4b8e7..5fbf60fde9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -128,10 +128,11 @@ export { ReadPreferenceMode } from './read_preference'; export { ServerType, TopologyType } from './sdam/common'; // Helper classes +export type { AWSCredentialProvider } from './cmap/auth/aws_temporary_credentials'; +export type { AWSCredentials } from './deps'; export { ReadConcern } from './read_concern'; export { ReadPreference } from './read_preference'; export { WriteConcern } from './write_concern'; - // events export { CommandFailedEvent, diff --git a/src/mongo_client_auth_providers.ts b/src/mongo_client_auth_providers.ts index c23d515e17a..9e4d6656eb1 100644 --- a/src/mongo_client_auth_providers.ts +++ b/src/mongo_client_auth_providers.ts @@ -1,4 +1,5 @@ import { type AuthProvider } from './cmap/auth/auth_provider'; +import { type AWSCredentialProvider } from './cmap/auth/aws_temporary_credentials'; import { GSSAPI } from './cmap/auth/gssapi'; import { type AuthMechanismProperties } from './cmap/auth/mongo_credentials'; import { MongoDBAWS } from './cmap/auth/mongodb_aws'; @@ -13,8 +14,11 @@ import { X509 } from './cmap/auth/x509'; import { MongoInvalidArgumentError } from './error'; /** @internal */ -const AUTH_PROVIDERS = new Map AuthProvider>([ - [AuthMechanism.MONGODB_AWS, () => new MongoDBAWS()], +const AUTH_PROVIDERS = new Map AuthProvider>([ + [ + AuthMechanism.MONGODB_AWS, + (credentialProvider?: AWSCredentialProvider) => new MongoDBAWS(credentialProvider) + ], [ AuthMechanism.MONGODB_CR, () => { @@ -65,6 +69,8 @@ export class MongoClientAuthProviders { let provider; if (name === AuthMechanism.MONGODB_OIDC) { provider = providerFunction(this.getWorkflow(authMechanismProperties)); + } else if (name === AuthMechanism.MONGODB_AWS) { + provider = providerFunction(authMechanismProperties.AWS_CREDENTIAL_PROVIDER); } else { provider = providerFunction(); } diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 74feeff48fc..601191bb75b 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -136,6 +136,34 @@ describe('MONGODB-AWS', function () { }); }); + context('when user supplies a credentials provider', function () { + beforeEach(function () { + if (!awsSdkPresent) { + this.skipReason = 'only relevant to AssumeRoleWithWebIdentity with SDK installed'; + return this.skip(); + } + }); + + it('authenticates with a user provided credentials provider', async function () { + // @ts-expect-error We intentionally access a protected variable. + const credentialProvider = AWSTemporaryCredentialProvider.awsSDK; + client = this.configuration.newClient(process.env.MONGODB_URI, { + authMechanismProperties: { + AWS_CREDENTIAL_PROVIDER: credentialProvider.fromNodeProviderChain() + } + }); + + 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'); + }); + }); + 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: '' } @@ -426,11 +454,33 @@ describe('AWS KMS Credential Fetching', function () { : undefined; this.currentTest?.skipReason && this.skip(); }); - it('KMS credentials are successfully fetched.', async function () { - const { aws } = await refreshKMSCredentials({ aws: {} }); - expect(aws).to.have.property('accessKeyId'); - expect(aws).to.have.property('secretAccessKey'); + context('when a credential provider is not providered', function () { + it('KMS credentials are successfully fetched.', async function () { + const { aws } = await refreshKMSCredentials({ aws: {} }); + + expect(aws).to.have.property('accessKeyId'); + expect(aws).to.have.property('secretAccessKey'); + }); + }); + + context('when a credential provider is provided', function () { + let credentialProvider; + + beforeEach(function () { + // @ts-expect-error We intentionally access a protected variable. + credentialProvider = AWSTemporaryCredentialProvider.awsSDK; + }); + + it('KMS credentials are successfully fetched.', async function () { + const { aws } = await refreshKMSCredentials( + { aws: {} }, + credentialProvider.fromNodeProviderChain() + ); + + expect(aws).to.have.property('accessKeyId'); + expect(aws).to.have.property('secretAccessKey'); + }); }); it('does not return any extra keys for the `aws` credential provider', async function () { From 36fe795c6b2c9436d12a39e3258387f73eded9ad Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Fri, 31 Jan 2025 17:49:52 +0100 Subject: [PATCH 02/25] feat: add auto encryption option --- src/client-side-encryption/auto_encrypter.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/client-side-encryption/auto_encrypter.ts b/src/client-side-encryption/auto_encrypter.ts index 1e63a5bedbf..19ee238a930 100644 --- a/src/client-side-encryption/auto_encrypter.ts +++ b/src/client-side-encryption/auto_encrypter.ts @@ -105,6 +105,8 @@ export interface AutoEncryptionOptions { proxyOptions?: ProxyOptions; /** The TLS options to use connecting to the KMS provider */ tlsOptions?: CSFLEKMSTlsOptions; + /** Optional custom credential provider to use for KMS requests. */ + awsCredentialProvider?: AWSCredentialProvider; } /** @@ -239,6 +241,7 @@ export class AutoEncrypter { this._proxyOptions = options.proxyOptions || {}; this._tlsOptions = options.tlsOptions || {}; this._kmsProviders = options.kmsProviders || {}; + this._awsCredentialProvider = options.awsCredentialProvider; const mongoCryptOptions: MongoCryptOptions = { enableMultipleCollinfo: true, @@ -330,11 +333,6 @@ export class AutoEncrypter { * This function is a no-op when bypassSpawn is set or the crypt shared library is used. */ async init(): Promise { - // This is handled during init() as the auto encrypter is instantiated during the client's - // parseOptions() call, so the client doesn't have its options set at that point. - this._awsCredentialProvider = - this._client.options.credentials?.mechanismProperties.AWS_CREDENTIAL_PROVIDER; - if (this._bypassMongocryptdAndCryptShared || this.cryptSharedLibVersionInfo) { return; } From 2d76be7908b840620967472b18d9ad20668946d5 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 20 Feb 2025 23:28:56 +0100 Subject: [PATCH 03/25] feat: use credential providers --- src/client-side-encryption/auto_encrypter.ts | 13 ++++++------- src/client-side-encryption/client_encryption.ts | 14 +++++++++----- src/client-side-encryption/providers/index.ts | 13 +++++++++++-- src/index.ts | 1 + test/integration/auth/mongodb_aws.test.ts | 2 +- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/client-side-encryption/auto_encrypter.ts b/src/client-side-encryption/auto_encrypter.ts index 19ee238a930..5174a851e90 100644 --- a/src/client-side-encryption/auto_encrypter.ts +++ b/src/client-side-encryption/auto_encrypter.ts @@ -6,7 +6,6 @@ import { import * as net from 'net'; import { deserialize, type Document, serialize } from '../bson'; -import { type AWSCredentialProvider } from '../cmap/auth/aws_temporary_credentials'; import { type CommandOptions, type ProxyOptions } from '../cmap/connection'; import { kDecorateResult } from '../constants'; import { getMongoDBClientEncryption } from '../deps'; @@ -18,7 +17,7 @@ import { autoSelectSocketOptions } from './client_encryption'; import * as cryptoCallbacks from './crypto_callbacks'; import { MongoCryptInvalidArgumentError } from './errors'; import { MongocryptdManager } from './mongocryptd_manager'; -import { type KMSProviders, refreshKMSCredentials } from './providers'; +import { type CredentialProviders, type KMSProviders, refreshKMSCredentials } from './providers'; import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine'; /** @public */ @@ -31,6 +30,8 @@ export interface AutoEncryptionOptions { keyVaultNamespace?: string; /** Configuration options that are used by specific KMS providers during key generation, encryption, and decryption. */ kmsProviders?: KMSProviders; + /** Configuration options for custom credential providers. */ + credentialProviders?: CredentialProviders; /** * A map of namespaces to a local JSON schema for encryption * @@ -105,8 +106,6 @@ export interface AutoEncryptionOptions { proxyOptions?: ProxyOptions; /** The TLS options to use connecting to the KMS provider */ tlsOptions?: CSFLEKMSTlsOptions; - /** Optional custom credential provider to use for KMS requests. */ - awsCredentialProvider?: AWSCredentialProvider; } /** @@ -156,7 +155,7 @@ export class AutoEncrypter { _kmsProviders: KMSProviders; _bypassMongocryptdAndCryptShared: boolean; _contextCounter: number; - _awsCredentialProvider?: AWSCredentialProvider; + _credentialProviders?: CredentialProviders; _mongocryptdManager?: MongocryptdManager; _mongocryptdClient?: MongoClient; @@ -241,7 +240,7 @@ export class AutoEncrypter { this._proxyOptions = options.proxyOptions || {}; this._tlsOptions = options.tlsOptions || {}; this._kmsProviders = options.kmsProviders || {}; - this._awsCredentialProvider = options.awsCredentialProvider; + this._credentialProviders = options.credentialProviders; const mongoCryptOptions: MongoCryptOptions = { enableMultipleCollinfo: true, @@ -444,7 +443,7 @@ export class AutoEncrypter { * the original ones. */ async askForKMSCredentials(): Promise { - return await refreshKMSCredentials(this._kmsProviders, this._awsCredentialProvider); + return await refreshKMSCredentials(this._kmsProviders, this._credentialProviders); } /** diff --git a/src/client-side-encryption/client_encryption.ts b/src/client-side-encryption/client_encryption.ts index d6c7115cb6c..04c7e94586d 100644 --- a/src/client-side-encryption/client_encryption.ts +++ b/src/client-side-encryption/client_encryption.ts @@ -15,7 +15,6 @@ import { type UUID } from '../bson'; import { type AnyBulkWriteOperation, type BulkWriteResult } from '../bulk/common'; -import { type AWSCredentialProvider } from '../cmap/auth/aws_temporary_credentials'; import { type ProxyOptions } from '../cmap/connection'; import { type Collection } from '../collection'; import { type FindCursor } from '../cursor/find_cursor'; @@ -35,6 +34,7 @@ import { } from './errors'; import { type ClientEncryptionDataKeyProvider, + type CredentialProviders, type KMSProviders, refreshKMSCredentials } from './providers/index'; @@ -83,7 +83,7 @@ export class ClientEncryption { _mongoCrypt: MongoCrypt; /** @internal */ - _awsCredentialProvider?: AWSCredentialProvider; + _credentialProviders?: CredentialProviders; /** @internal */ static getMongoCrypt(): MongoCryptConstructor { @@ -129,8 +129,7 @@ export class ClientEncryption { this._kmsProviders = options.kmsProviders || {}; const { timeoutMS } = resolveTimeoutOptions(client, options); this._timeoutMS = timeoutMS; - this._awsCredentialProvider = - client.options.credentials?.mechanismProperties.AWS_CREDENTIAL_PROVIDER; + this._credentialProviders = options.credentialProviders; if (options.keyVaultNamespace == null) { throw new MongoCryptInvalidArgumentError('Missing required option `keyVaultNamespace`'); @@ -718,7 +717,7 @@ export class ClientEncryption { * the original ones. */ async askForKMSCredentials(): Promise { - return await refreshKMSCredentials(this._kmsProviders, this._awsCredentialProvider); + return await refreshKMSCredentials(this._kmsProviders, this._credentialProviders); } static get libmongocryptVersion() { @@ -864,6 +863,11 @@ export interface ClientEncryptionOptions { */ kmsProviders?: KMSProviders; + /** + * Options for user provided custom credential providers. + */ + credentialProviders?: CredentialProviders; + /** * Options for specifying a Socks5 proxy to use for connecting to the KMS. */ diff --git a/src/client-side-encryption/providers/index.ts b/src/client-side-encryption/providers/index.ts index b6590b16425..63108c7ed93 100644 --- a/src/client-side-encryption/providers/index.ts +++ b/src/client-side-encryption/providers/index.ts @@ -113,6 +113,15 @@ export type GCPKMSProviderConfiguration = accessToken: string; }; +/** + * @public + * Configuration options for custom credential providers for KMS requests. + */ +export interface CredentialProviders { + /* A custom AWS credential provider */ + aws?: AWSCredentialProvider; +} + /** * @public * Configuration options that are used by specific KMS providers during key generation, encryption, and decryption. @@ -179,12 +188,12 @@ export function isEmptyCredentials( */ export async function refreshKMSCredentials( kmsProviders: KMSProviders, - awsProvider?: AWSCredentialProvider + credentialProviders?: CredentialProviders ): Promise { let finalKMSProviders = kmsProviders; if (isEmptyCredentials('aws', kmsProviders)) { - finalKMSProviders = await loadAWSCredentials(finalKMSProviders, awsProvider); + finalKMSProviders = await loadAWSCredentials(finalKMSProviders, credentialProviders?.aws); } if (isEmptyCredentials('gcp', kmsProviders)) { diff --git a/src/index.ts b/src/index.ts index 5fbf60fde9b..476b5affc3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -256,6 +256,7 @@ export type { AWSKMSProviderConfiguration, AzureKMSProviderConfiguration, ClientEncryptionDataKeyProvider, + CredentialProviders, GCPKMSProviderConfiguration, KMIPKMSProviderConfiguration, KMSProviders, diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 601191bb75b..1b7b6259013 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -475,7 +475,7 @@ describe('AWS KMS Credential Fetching', function () { it('KMS credentials are successfully fetched.', async function () { const { aws } = await refreshKMSCredentials( { aws: {} }, - credentialProvider.fromNodeProviderChain() + { aws: credentialProvider.fromNodeProviderChain() } ); expect(aws).to.have.property('accessKeyId'); From 73584b272f7aa4747fd7c5a154613d19b264cc7a Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 24 Feb 2025 11:54:56 +0100 Subject: [PATCH 04/25] test: add config tests --- .../client_encryption.ts | 10 +++ .../client-side-encryption/driver.test.ts | 61 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/client-side-encryption/client_encryption.ts b/src/client-side-encryption/client_encryption.ts index 04c7e94586d..69c5572c4a4 100644 --- a/src/client-side-encryption/client_encryption.ts +++ b/src/client-side-encryption/client_encryption.ts @@ -35,6 +35,7 @@ import { import { type ClientEncryptionDataKeyProvider, type CredentialProviders, + isEmptyCredentials, type KMSProviders, refreshKMSCredentials } from './providers/index'; @@ -131,6 +132,15 @@ export class ClientEncryption { this._timeoutMS = timeoutMS; this._credentialProviders = options.credentialProviders; + if ( + options.credentialProviders?.aws && + !isEmptyCredentials('aws', options.kmsProviders || {}) + ) { + throw new MongoCryptInvalidArgumentError( + 'Cannot provide both a custom credential provider and credentials. Please specify one or the other.' + ); + } + if (options.keyVaultNamespace == null) { throw new MongoCryptInvalidArgumentError('Missing required option `keyVaultNamespace`'); } diff --git a/test/integration/client-side-encryption/driver.test.ts b/test/integration/client-side-encryption/driver.test.ts index 2854dd8912e..7f3161ed65c 100644 --- a/test/integration/client-side-encryption/driver.test.ts +++ b/test/integration/client-side-encryption/driver.test.ts @@ -50,6 +50,67 @@ describe('Client Side Encryption Functional', function () { const keyVaultCollName = 'datakeys'; const keyVaultNamespace = `${keyVaultDbName}.${keyVaultCollName}`; + describe('ClientEncryption', metadata, function () { + describe('#constructor', function () { + context('when a custom credential provider and credentials are provided', function () { + let client; + + before(function () { + client = this.configuration.newClient({}); + }); + + it('throws an error', function () { + expect(() => { + new ClientEncryption(client, { + keyVaultNamespace: 'test.keyvault', + kmsProviders: { + aws: { secretAccessKey: 'test', accessKeyId: 'test' } + }, + credentialProviders: { + aws: async () => { + return { + sessionToken: 'test', + secretAccessKey: 'test', + accessKeyId: 'test' + }; + } + } + }); + }).to.throw(/custom credential provider and credentials/); + }); + }); + }); + }); + + describe('AutoEncrypter', metadata, function () { + context('when a custom credential provider and credentials are provided', function () { + it('throws an error', function () { + expect(() => { + this.configuration.newClient( + {}, + { + autoEncryption: { + keyVaultNamespace: 'test.keyvault', + kmsProviders: { + aws: { secretAccessKey: 'test', accessKeyId: 'test' } + }, + credentialProviders: { + aws: async () => { + return { + sessionToken: 'test', + secretAccessKey: 'test', + accessKeyId: 'test' + }; + } + } + } + } + ); + }).to.throw(/custom credential provider and credentials/); + }); + }); + }); + describe('Collection', metadata, function () { describe('#bulkWrite()', metadata, function () { context('when encryption errors', function () { From 35303e8d2af3b6f3c4c72e5164483ba0c66618f0 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 24 Feb 2025 12:11:26 +0100 Subject: [PATCH 05/25] test: add config tests --- src/client-side-encryption/auto_encrypter.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/client-side-encryption/auto_encrypter.ts b/src/client-side-encryption/auto_encrypter.ts index 5174a851e90..19a79c39552 100644 --- a/src/client-side-encryption/auto_encrypter.ts +++ b/src/client-side-encryption/auto_encrypter.ts @@ -17,7 +17,12 @@ import { autoSelectSocketOptions } from './client_encryption'; import * as cryptoCallbacks from './crypto_callbacks'; import { MongoCryptInvalidArgumentError } from './errors'; import { MongocryptdManager } from './mongocryptd_manager'; -import { type CredentialProviders, type KMSProviders, refreshKMSCredentials } from './providers'; +import { + type CredentialProviders, + isEmptyCredentials, + type KMSProviders, + refreshKMSCredentials +} from './providers'; import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine'; /** @public */ @@ -242,6 +247,15 @@ export class AutoEncrypter { this._kmsProviders = options.kmsProviders || {}; this._credentialProviders = options.credentialProviders; + if ( + options.credentialProviders?.aws && + !isEmptyCredentials('aws', options.kmsProviders || {}) + ) { + throw new MongoCryptInvalidArgumentError( + 'Cannot provide both a custom credential provider and credentials. Please specify one or the other.' + ); + } + const mongoCryptOptions: MongoCryptOptions = { enableMultipleCollinfo: true, cryptoCallbacks From 45115cd2cc9d2927956c18316b4da66b08a5f20b Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 27 Feb 2025 14:11:39 +0100 Subject: [PATCH 06/25] test: prose tests --- .evergreen/config.in.yml | 15 +++ .evergreen/config.yml | 30 +++++ .evergreen/generate_evergreen_tasks.js | 4 + ...un-aws-custom-credential-providers-test.sh | 14 +++ src/deps.ts | 2 +- ...25.custom_aws_credential_providers.test.ts | 111 ++++++++++++++++++ 6 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 .evergreen/run-aws-custom-credential-providers-test.sh create mode 100644 test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index 1a3adee09d2..79814a3058e 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -557,6 +557,21 @@ functions: args: - .evergreen/prepare-mongodb-aws-ecs-auth.sh + "run aws custom credential providers test": + - command: subprocess.exec + type: test + params: + include_expansions_in_env: + - MONGODB_URI + - DRIVERS_TOOLS + - MONGODB_AWS_SDK + env: + AWS_CREDENTIAL_TYPE: env-creds + working_dir: "src" + binary: bash + args: + - .evergreen/run-aws-custom-credential-providers-test.sh + "run custom csfle tests": - command: subprocess.exec type: test diff --git a/.evergreen/config.yml b/.evergreen/config.yml index d9f3b3a1f73..78e0e7af6c4 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -493,6 +493,20 @@ functions: binary: bash args: - .evergreen/prepare-mongodb-aws-ecs-auth.sh + run aws custom credential providers test: + - command: subprocess.exec + type: test + params: + include_expansions_in_env: + - MONGODB_URI + - DRIVERS_TOOLS + - MONGODB_AWS_SDK + env: + AWS_CREDENTIAL_TYPE: env-creds + working_dir: src + binary: bash + args: + - .evergreen/run-aws-custom-credential-providers-test.sh run custom csfle tests: - command: subprocess.exec type: test @@ -1805,6 +1819,21 @@ tasks: - func: bootstrap mongo-orchestration - func: assume secrets manager rule - func: run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME set + - name: aws-latest-auth-test-run-aws-custom-credential-providers-test + 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: 'true'} + - func: install dependencies + - func: bootstrap mongo-orchestration + - func: assume secrets manager rule + - func: run aws custom credential providers test - name: aws-latest-auth-test-run-aws-auth-test-with-regular-aws-credentials-no-peer-dependencies commands: - command: expansions.update @@ -3686,6 +3715,7 @@ 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-custom-credential-providers-test - 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 diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 87ac59b9086..93f48dd3399 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -351,6 +351,10 @@ for (const VERSION of AWS_AUTH_VERSIONS) { { func: 'run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME set', onlySdk: true + }, + { + func: 'run aws custom credential providers test', + onlySdk: true } ]; diff --git a/.evergreen/run-aws-custom-credential-providers-test.sh b/.evergreen/run-aws-custom-credential-providers-test.sh new file mode 100644 index 00000000000..4173e5f0fc6 --- /dev/null +++ b/.evergreen/run-aws-custom-credential-providers-test.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# set -o xtrace # Write all commands first to stderr +set -o errexit # Exit the script with error if any of the commands fail + +MONGODB_URI=${MONGODB_URI:-} + +source .evergreen/setup-mongodb-aws-auth-tests.sh + +# load node.js environment +source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh + +export TEST_CSFLE=true + +npx mocha --config test/mocha_mongodb.js test/integration/client-side-encryption/client_side_encryption.prose.25.custon_aws_credential_providers.test.ts \ No newline at end of file diff --git a/src/deps.ts b/src/deps.ts index f3064424308..e9f4a42e39f 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -85,7 +85,7 @@ export function getZstdLibrary(): ZStandardLib | { kModuleError: MongoMissingDep export interface AWSCredentials { accessKeyId: string; secretAccessKey: string; - sessionToken: string; + sessionToken?: string; expiration?: Date; } diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts new file mode 100644 index 00000000000..37b68982459 --- /dev/null +++ b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts @@ -0,0 +1,111 @@ +import { expect } from 'chai'; + +/* eslint-disable @typescript-eslint/no-restricted-imports */ +import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption'; +import { AWSTemporaryCredentialProvider, Binary } from '../../mongodb'; + +const metadata: MongoDBMetadataUI = { + requires: { + clientSideEncryption: true + } +} as const; + +const masterKey = { + region: 'us-east-1', + key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0', + endpoint: '127.0.0.1:9002' +}; + +const isMongoDBAWSAuthEnvironment = (process.env.MONGODB_URI ?? '').includes('MONGODB-AWS'); + +describe('25. Custom AWS Credential Providers', metadata, () => { + let keyVaultClient; + let credentialProvider; + + beforeEach(async 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(); + + keyVaultClient = this.configuration.newClient(process.env.MONGODB_UR); + // @ts-expect-error We intentionally access a protected variable. + credentialProvider = AWSTemporaryCredentialProvider.awsSDK; + }); + + afterEach(async () => { + await keyVaultClient?.close(); + }); + + context( + 'Case 1: Explicit encryption with credentials and custom credential provider', + function () { + it('throws an error', function () { + expect(() => { + new ClientEncryption(keyVaultClient, { + keyVaultNamespace: 'keyvault.datakeys', + kmsProviders: { + aws: { + accessKeyId: process.env.FLE_AWS_KEY, + secretAccessKey: process.env.FLE_AWS_SECRET + } + }, + credentialProviders: { aws: credentialProvider.fromNodeProviderChain() } + }); + }).to.throw(); + }); + } + ); + + context('Case 2: Explicit encryption with custom credential provider', function () { + let clientEncryption; + + beforeEach(function () { + clientEncryption = new ClientEncryption(keyVaultClient, { + keyVaultNamespace: 'keyvault.datakeys', + kmsProviders: { aws: {} }, + credentialProviders: { aws: credentialProvider.fromNodeProviderChain() } + }); + }); + + it('is successful', async function () { + const dk = await clientEncryption.createDataKey('aws', masterKey); + expect(dk).to.be.instanceOf(Binary); + }); + }); + + context('Case 3: Automatic encryption with different custom providers', function () { + let client; + + beforeEach(function () { + client = this.configuration.newClient(process.env.MONGODB_URI, { + authMechanismProperties: { + AWS_CREDENTIAL_PROVIDER: credentialProvider.fromNodeProviderChain() + }, + autoEncryption: { + keyVaultNamespace: 'keyvault.datakeys', + kmsProviders: { aws: {} }, + credentialProviders: { + aws: async () => { + return { + accessKeyId: process.env.FLE_AWS_KEY, + secretAccessKey: process.env.FLE_AWS_SECRET + }; + } + } + } + }); + }); + + afterEach(async function () { + await client?.close(); + }); + + it('is successful', async function () { + const result = await client.db('test').collection('test').insertOne({ n: 1 }); + expect(result.ok).to.equal(1); + }); + }); +}); From b7bb2bb80516e383366f73b79fe20327e4766df5 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 27 Feb 2025 14:31:34 +0100 Subject: [PATCH 07/25] fix: typo --- .evergreen/run-aws-custom-credential-providers-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/run-aws-custom-credential-providers-test.sh b/.evergreen/run-aws-custom-credential-providers-test.sh index 4173e5f0fc6..bc4f81e1978 100644 --- a/.evergreen/run-aws-custom-credential-providers-test.sh +++ b/.evergreen/run-aws-custom-credential-providers-test.sh @@ -11,4 +11,4 @@ source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh export TEST_CSFLE=true -npx mocha --config test/mocha_mongodb.js test/integration/client-side-encryption/client_side_encryption.prose.25.custon_aws_credential_providers.test.ts \ No newline at end of file +npx mocha --config test/mocha_mongodb.js test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts \ No newline at end of file From 42a2b75649b2929a2617c9f48ec142f8b2e0d2a3 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 27 Feb 2025 14:57:21 +0100 Subject: [PATCH 08/25] fix: change insert --- ...encryption.prose.25.custom_aws_credential_providers.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts index 37b68982459..e6f535194fc 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts @@ -104,8 +104,7 @@ describe('25. Custom AWS Credential Providers', metadata, () => { }); it('is successful', async function () { - const result = await client.db('test').collection('test').insertOne({ n: 1 }); - expect(result.ok).to.equal(1); + await client.db('aws').collection('aws_test').estimatedDocumentCount(); }); }); }); From 9c5d773d1c0a540f1d94d2f9ef0d23267ce6a49f Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 27 Feb 2025 15:01:55 +0100 Subject: [PATCH 09/25] fix: master key --- ...ryption.prose.25.custom_aws_credential_providers.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts index e6f535194fc..592c4c4585e 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts @@ -12,8 +12,7 @@ const metadata: MongoDBMetadataUI = { const masterKey = { region: 'us-east-1', - key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0', - endpoint: '127.0.0.1:9002' + key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0' }; const isMongoDBAWSAuthEnvironment = (process.env.MONGODB_URI ?? '').includes('MONGODB-AWS'); @@ -104,7 +103,8 @@ describe('25. Custom AWS Credential Providers', metadata, () => { }); it('is successful', async function () { - await client.db('aws').collection('aws_test').estimatedDocumentCount(); + const result = await client.db('test').collection('test').insertOne({ n: 1 }); + expect(result.ok).to.equal(1); }); }); }); From e0912f0ced53cfb6561a21438ffddfc9b58e39e7 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 27 Feb 2025 15:36:49 +0100 Subject: [PATCH 10/25] test: setup fle --- .evergreen/run-aws-custom-credential-providers-test.sh | 1 + ...ncryption.prose.25.custom_aws_credential_providers.test.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.evergreen/run-aws-custom-credential-providers-test.sh b/.evergreen/run-aws-custom-credential-providers-test.sh index bc4f81e1978..da833ca8230 100644 --- a/.evergreen/run-aws-custom-credential-providers-test.sh +++ b/.evergreen/run-aws-custom-credential-providers-test.sh @@ -5,6 +5,7 @@ set -o errexit # Exit the script with error if any of the commands fail MONGODB_URI=${MONGODB_URI:-} source .evergreen/setup-mongodb-aws-auth-tests.sh +source .evergreen/setup-fle.sh # load node.js environment source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts index 592c4c4585e..923b70ea91b 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; /* eslint-disable @typescript-eslint/no-restricted-imports */ import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption'; import { AWSTemporaryCredentialProvider, Binary } from '../../mongodb'; +import { getEncryptExtraOptions } from '../../tools/utils'; const metadata: MongoDBMetadataUI = { requires: { @@ -93,7 +94,8 @@ describe('25. Custom AWS Credential Providers', metadata, () => { secretAccessKey: process.env.FLE_AWS_SECRET }; } - } + }, + extraOptions: getEncryptExtraOptions() } }); }); From 0be1f1f1eecec60525ab119c173dc2bc6f360b5b Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 27 Feb 2025 15:43:41 +0100 Subject: [PATCH 11/25] test: setup fle first --- .evergreen/run-aws-custom-credential-providers-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/run-aws-custom-credential-providers-test.sh b/.evergreen/run-aws-custom-credential-providers-test.sh index da833ca8230..a9ad9e362ce 100644 --- a/.evergreen/run-aws-custom-credential-providers-test.sh +++ b/.evergreen/run-aws-custom-credential-providers-test.sh @@ -4,8 +4,8 @@ set -o errexit # Exit the script with error if any of the commands fail MONGODB_URI=${MONGODB_URI:-} -source .evergreen/setup-mongodb-aws-auth-tests.sh source .evergreen/setup-fle.sh +source .evergreen/setup-mongodb-aws-auth-tests.sh # load node.js environment source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh From 9b2c56388e0436cfc6c129adcc66537fa8fbda85 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 27 Feb 2025 16:01:09 +0100 Subject: [PATCH 12/25] const doc = await cursor.next(); test: skip fle setup --- .evergreen/run-aws-custom-credential-providers-test.sh | 7 ++++++- ...yption.prose.25.custom_aws_credential_providers.test.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.evergreen/run-aws-custom-credential-providers-test.sh b/.evergreen/run-aws-custom-credential-providers-test.sh index a9ad9e362ce..4337d0bd0f6 100644 --- a/.evergreen/run-aws-custom-credential-providers-test.sh +++ b/.evergreen/run-aws-custom-credential-providers-test.sh @@ -4,8 +4,13 @@ set -o errexit # Exit the script with error if any of the commands fail MONGODB_URI=${MONGODB_URI:-} +# . $DRIVERS_TOOLS/.evergreen/secrets_handling/setup-secrets.sh drivers/aws_auth + +export source .evergreen/setup-fle.sh -source .evergreen/setup-mongodb-aws-auth-tests.sh + +#export +#source .evergreen/setup-mongodb-aws-auth-tests.sh # load node.js environment source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts index 923b70ea91b..15bbf7b9cf7 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts @@ -76,7 +76,7 @@ describe('25. Custom AWS Credential Providers', metadata, () => { }); }); - context('Case 3: Automatic encryption with different custom providers', function () { + context.skip('Case 3: Automatic encryption with different custom providers', function () { let client; beforeEach(function () { From 33b8489e602e2a2ddde2c09984868a5ab4986043 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 3 Mar 2025 14:05:40 +0100 Subject: [PATCH 13/25] test: run with fle tests --- .evergreen/config.yml | 16 ---------------- .evergreen/generate_evergreen_tasks.js | 4 ---- ...e.26.custom_aws_credential_providers.test.ts} | 10 +++------- 3 files changed, 3 insertions(+), 27 deletions(-) rename test/integration/client-side-encryption/{client_side_encryption.prose.25.custom_aws_credential_providers.test.ts => client_side_encryption.prose.26.custom_aws_credential_providers.test.ts} (89%) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 78e0e7af6c4..cde4313c3da 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1819,21 +1819,6 @@ tasks: - func: bootstrap mongo-orchestration - func: assume secrets manager rule - func: run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME set - - name: aws-latest-auth-test-run-aws-custom-credential-providers-test - 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: 'true'} - - func: install dependencies - - func: bootstrap mongo-orchestration - - func: assume secrets manager rule - - func: run aws custom credential providers test - name: aws-latest-auth-test-run-aws-auth-test-with-regular-aws-credentials-no-peer-dependencies commands: - command: expansions.update @@ -3715,7 +3700,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-custom-credential-providers-test - 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 diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 93f48dd3399..87ac59b9086 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -351,10 +351,6 @@ for (const VERSION of AWS_AUTH_VERSIONS) { { func: 'run aws auth test AssumeRoleWithWebIdentity with AWS_ROLE_SESSION_NAME set', onlySdk: true - }, - { - func: 'run aws custom credential providers test', - onlySdk: true } ]; diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts similarity index 89% rename from test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts rename to test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts index 15bbf7b9cf7..491221ebb9a 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.26.custom_aws_credential_providers.test.ts @@ -16,18 +16,14 @@ const masterKey = { key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0' }; -const isMongoDBAWSAuthEnvironment = (process.env.MONGODB_URI ?? '').includes('MONGODB-AWS'); - describe('25. Custom AWS Credential Providers', metadata, () => { let keyVaultClient; let credentialProvider; beforeEach(async 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 = !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); From e563fdb9868eee919bdf5c5e7e49c93209116b5f Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 3 Mar 2025 14:34:29 +0100 Subject: [PATCH 14/25] test: cleanup --- .evergreen/config.in.yml | 15 -------------- .evergreen/config.yml | 14 ------------- ...un-aws-custom-credential-providers-test.sh | 20 ------------------- ...26.custom_aws_credential_providers.test.ts | 11 ++++++---- 4 files changed, 7 insertions(+), 53 deletions(-) delete mode 100644 .evergreen/run-aws-custom-credential-providers-test.sh diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index 79814a3058e..1a3adee09d2 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -557,21 +557,6 @@ functions: args: - .evergreen/prepare-mongodb-aws-ecs-auth.sh - "run aws custom credential providers test": - - command: subprocess.exec - type: test - params: - include_expansions_in_env: - - MONGODB_URI - - DRIVERS_TOOLS - - MONGODB_AWS_SDK - env: - AWS_CREDENTIAL_TYPE: env-creds - working_dir: "src" - binary: bash - args: - - .evergreen/run-aws-custom-credential-providers-test.sh - "run custom csfle tests": - command: subprocess.exec type: test diff --git a/.evergreen/config.yml b/.evergreen/config.yml index cde4313c3da..d9f3b3a1f73 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -493,20 +493,6 @@ functions: binary: bash args: - .evergreen/prepare-mongodb-aws-ecs-auth.sh - run aws custom credential providers test: - - command: subprocess.exec - type: test - params: - include_expansions_in_env: - - MONGODB_URI - - DRIVERS_TOOLS - - MONGODB_AWS_SDK - env: - AWS_CREDENTIAL_TYPE: env-creds - working_dir: src - binary: bash - args: - - .evergreen/run-aws-custom-credential-providers-test.sh run custom csfle tests: - command: subprocess.exec type: test diff --git a/.evergreen/run-aws-custom-credential-providers-test.sh b/.evergreen/run-aws-custom-credential-providers-test.sh deleted file mode 100644 index 4337d0bd0f6..00000000000 --- a/.evergreen/run-aws-custom-credential-providers-test.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# set -o xtrace # Write all commands first to stderr -set -o errexit # Exit the script with error if any of the commands fail - -MONGODB_URI=${MONGODB_URI:-} - -# . $DRIVERS_TOOLS/.evergreen/secrets_handling/setup-secrets.sh drivers/aws_auth - -export -source .evergreen/setup-fle.sh - -#export -#source .evergreen/setup-mongodb-aws-auth-tests.sh - -# load node.js environment -source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh - -export TEST_CSFLE=true - -npx mocha --config test/mocha_mongodb.js test/integration/client-side-encryption/client_side_encryption.prose.25.custom_aws_credential_providers.test.ts \ No newline at end of file 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 491221ebb9a..ff726ad04cd 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,6 +2,7 @@ import { expect } from 'chai'; /* eslint-disable @typescript-eslint/no-restricted-imports */ import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption'; +import { getCSFLEKMSProviders } from '../../csfle-kms-providers'; import { AWSTemporaryCredentialProvider, Binary } from '../../mongodb'; import { getEncryptExtraOptions } from '../../tools/utils'; @@ -59,11 +60,13 @@ describe('25. Custom AWS Credential Providers', metadata, () => { let clientEncryption; beforeEach(function () { - clientEncryption = new ClientEncryption(keyVaultClient, { + const options = { keyVaultNamespace: 'keyvault.datakeys', - kmsProviders: { aws: {} }, - credentialProviders: { aws: credentialProvider.fromNodeProviderChain() } - }); + kmsProviders: getCSFLEKMSProviders(), + credentialProviders: { aws: credentialProvider.fromNodeProviderChain() }, + extraOptions: getEncryptExtraOptions() + }; + clientEncryption = new ClientEncryption(keyVaultClient, options); }); it('is successful', async function () { From af400514d6c5c750d2763da6bd6d19f8c852b2a4 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 3 Mar 2025 14:47:46 +0100 Subject: [PATCH 15/25] fix: test --- ...encryption.prose.26.custom_aws_credential_providers.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 ff726ad04cd..88ba82388ab 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,6 @@ import { expect } from 'chai'; /* eslint-disable @typescript-eslint/no-restricted-imports */ import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption'; -import { getCSFLEKMSProviders } from '../../csfle-kms-providers'; import { AWSTemporaryCredentialProvider, Binary } from '../../mongodb'; import { getEncryptExtraOptions } from '../../tools/utils'; @@ -62,7 +61,7 @@ describe('25. Custom AWS Credential Providers', metadata, () => { beforeEach(function () { const options = { keyVaultNamespace: 'keyvault.datakeys', - kmsProviders: getCSFLEKMSProviders(), + kmsProviders: { aws: {} }, credentialProviders: { aws: credentialProvider.fromNodeProviderChain() }, extraOptions: getEncryptExtraOptions() }; From b4ef7025a540795eacc026c9190ab4ad431eec03 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 3 Mar 2025 15:15:02 +0100 Subject: [PATCH 16/25] fix: test --- ..._encryption.prose.26.custom_aws_credential_providers.test.ts | 2 +- 1 file changed, 1 insertion(+), 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 88ba82388ab..7c2c57c31af 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 @@ -69,7 +69,7 @@ describe('25. Custom AWS Credential Providers', metadata, () => { }); it('is successful', async function () { - const dk = await clientEncryption.createDataKey('aws', masterKey); + const dk = await clientEncryption.createDataKey('aws', { masterKey }); expect(dk).to.be.instanceOf(Binary); }); }); From 4ec025e10ec5dbe91cf47a3fc333061e527a7233 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 3 Mar 2025 16:10:21 +0100 Subject: [PATCH 17/25] test: fix provider --- ...26.custom_aws_credential_providers.test.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) 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 7c2c57c31af..ad24f5bc33f 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 @@ -7,7 +7,9 @@ import { getEncryptExtraOptions } from '../../tools/utils'; const metadata: MongoDBMetadataUI = { requires: { - clientSideEncryption: true + clientSideEncryption: true, + mongodb: '>=4.2.0', + topology: '!load-balanced' } } as const; @@ -37,8 +39,9 @@ describe('25. Custom AWS Credential Providers', metadata, () => { context( 'Case 1: Explicit encryption with credentials and custom credential provider', + metadata, function () { - it('throws an error', function () { + it('throws an error', metadata, function () { expect(() => { new ClientEncryption(keyVaultClient, { keyVaultNamespace: 'keyvault.datakeys', @@ -55,20 +58,27 @@ describe('25. Custom AWS Credential Providers', metadata, () => { } ); - context('Case 2: Explicit encryption with custom credential provider', function () { + context('Case 2: Explicit encryption with custom credential provider', metadata, function () { let clientEncryption; beforeEach(function () { const options = { keyVaultNamespace: 'keyvault.datakeys', kmsProviders: { aws: {} }, - credentialProviders: { aws: credentialProvider.fromNodeProviderChain() }, + credentialProviders: { + aws: async () => { + return { + accessKeyId: process.env.FLE_AWS_KEY, + secretAccessKey: process.env.FLE_AWS_SECRET + }; + } + }, extraOptions: getEncryptExtraOptions() }; clientEncryption = new ClientEncryption(keyVaultClient, options); }); - it('is successful', async function () { + it('is successful', metadata, async function () { const dk = await clientEncryption.createDataKey('aws', { masterKey }); expect(dk).to.be.instanceOf(Binary); }); From e3e2681009a5e7402bedac5e0419cb09d21afeb8 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Sun, 9 Mar 2025 20:05:20 +0100 Subject: [PATCH 18/25] test: add auto encryption test --- ...26.custom_aws_credential_providers.test.ts | 55 ++++++++----------- 1 file changed, 22 insertions(+), 33 deletions(-) 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 ad24f5bc33f..97e91f15563 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 } from '../../mongodb'; +import { AWSTemporaryCredentialProvider, Binary, MongoClient } from '../../mongodb'; import { getEncryptExtraOptions } from '../../tools/utils'; const metadata: MongoDBMetadataUI = { @@ -53,7 +53,7 @@ describe('25. Custom AWS Credential Providers', metadata, () => { }, credentialProviders: { aws: credentialProvider.fromNodeProviderChain() } }); - }).to.throw(); + }).to.throw(/custom credential provider and credentials/); }); } ); @@ -84,37 +84,26 @@ describe('25. Custom AWS Credential Providers', metadata, () => { }); }); - context.skip('Case 3: Automatic encryption with different custom providers', function () { - let client; - - beforeEach(function () { - client = this.configuration.newClient(process.env.MONGODB_URI, { - authMechanismProperties: { - AWS_CREDENTIAL_PROVIDER: credentialProvider.fromNodeProviderChain() - }, - autoEncryption: { - keyVaultNamespace: 'keyvault.datakeys', - kmsProviders: { aws: {} }, - credentialProviders: { - aws: async () => { - return { - accessKeyId: process.env.FLE_AWS_KEY, - secretAccessKey: process.env.FLE_AWS_SECRET - }; + context( + 'Case 3: Auto encryption with credentials and custom credential provider', + metadata, + function () { + it('throws an error', metadata, function () { + expect(() => { + new MongoClient('mongodb://127.0.0.1:27017', { + autoEncryption: { + keyVaultNamespace: 'keyvault.datakeys', + kmsProviders: { + aws: { + accessKeyId: process.env.FLE_AWS_KEY, + secretAccessKey: process.env.FLE_AWS_SECRET + } + }, + credentialProviders: { aws: credentialProvider.fromNodeProviderChain() } } - }, - extraOptions: getEncryptExtraOptions() - } + }); + }).to.throw(/custom credential provider and credentials/); }); - }); - - afterEach(async function () { - await client?.close(); - }); - - it('is successful', async function () { - const result = await client.db('test').collection('test').insertOne({ n: 1 }); - expect(result.ok).to.equal(1); - }); - }); + } + ); }); From 074e4005e8f2b41d5a89c5f248a8aff8602b9dc4 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Sun, 9 Mar 2025 21:34:14 +0100 Subject: [PATCH 19/25] test: count provider calls --- src/cmap/auth/mongodb_aws.ts | 3 +++ test/integration/auth/mongodb_aws.test.ts | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index 34ec76b742e..9cb22c82caa 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -35,9 +35,12 @@ interface AWSSaslContinuePayload { export class MongoDBAWS extends AuthProvider { private credentialFetcher: AWSTemporaryCredentialProvider; + private credentialProvider?: AWSCredentialProvider; + constructor(credentialProvider?: AWSCredentialProvider) { super(); + this.credentialProvider = credentialProvider; this.credentialFetcher = AWSTemporaryCredentialProvider.isAWSSDKInstalled ? new AWSSDKCredentialProvider(credentialProvider) : new LegacyAWSTemporaryCredentialProvider(); diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 1b7b6259013..011bf15d43f 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -137,6 +137,8 @@ describe('MONGODB-AWS', function () { }); context('when user supplies a credentials provider', function () { + let providerCount = 0; + beforeEach(function () { if (!awsSdkPresent) { this.skipReason = 'only relevant to AssumeRoleWithWebIdentity with SDK installed'; @@ -147,9 +149,13 @@ 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++; + return await credentialProvider.fromNodeProviderChain().apply(); + }; client = this.configuration.newClient(process.env.MONGODB_URI, { authMechanismProperties: { - AWS_CREDENTIAL_PROVIDER: credentialProvider.fromNodeProviderChain() + AWS_CREDENTIAL_PROVIDER: provider } }); @@ -161,6 +167,13 @@ describe('MONGODB-AWS', function () { expect(result).to.not.be.instanceOf(MongoServerError); expect(result).to.be.a('number'); + // 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) { + expect(providerCount).to.equal(0); + } else { + expect(providerCount).to.be.greaterThan(0); + } }); }); From 2fb76c84705d81797587ebe947d19f3b0ad751ef Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Tue, 11 Mar 2025 18:18:33 +0000 Subject: [PATCH 20/25] test: check provider count --- ...encryption.prose.26.custom_aws_credential_providers.test.ts | 3 +++ 1 file changed, 3 insertions(+) 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 97e91f15563..75d4e02b884 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 @@ -60,6 +60,7 @@ describe('25. Custom AWS Credential Providers', metadata, () => { context('Case 2: Explicit encryption with custom credential provider', metadata, function () { let clientEncryption; + let providerCount = 0; beforeEach(function () { const options = { @@ -67,6 +68,7 @@ describe('25. Custom AWS Credential Providers', metadata, () => { kmsProviders: { aws: {} }, credentialProviders: { aws: async () => { + providerCount++; return { accessKeyId: process.env.FLE_AWS_KEY, secretAccessKey: process.env.FLE_AWS_SECRET @@ -81,6 +83,7 @@ describe('25. Custom AWS Credential Providers', metadata, () => { it('is successful', metadata, async function () { const dk = await clientEncryption.createDataKey('aws', { masterKey }); expect(dk).to.be.instanceOf(Binary); + expect(providerCount).to.be.greaterThan(0); }); }); From 2a284a96bcf351ccf1fb8bc22df85b92d44165e8 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Tue, 11 Mar 2025 18:40:11 +0000 Subject: [PATCH 21/25] chore: comments --- src/client-side-encryption/auto_encrypter.ts | 7 +-- .../client_encryption.ts | 7 +-- test/integration/auth/mongodb_aws.test.ts | 15 +++-- ...26.custom_aws_credential_providers.test.ts | 2 +- .../client-side-encryption/driver.test.ts | 61 ------------------- 5 files changed, 14 insertions(+), 78 deletions(-) diff --git a/src/client-side-encryption/auto_encrypter.ts b/src/client-side-encryption/auto_encrypter.ts index 19a79c39552..b8f2e42b13b 100644 --- a/src/client-side-encryption/auto_encrypter.ts +++ b/src/client-side-encryption/auto_encrypter.ts @@ -247,12 +247,9 @@ export class AutoEncrypter { this._kmsProviders = options.kmsProviders || {}; this._credentialProviders = options.credentialProviders; - if ( - options.credentialProviders?.aws && - !isEmptyCredentials('aws', options.kmsProviders || {}) - ) { + if (options.credentialProviders?.aws && !isEmptyCredentials('aws', this._kmsProviders)) { throw new MongoCryptInvalidArgumentError( - 'Cannot provide both a custom credential provider and credentials. Please specify one or the other.' + 'Can only provide a custom AWS credential provider when the state machine is configured for automatic AWS credential fetching' ); } diff --git a/src/client-side-encryption/client_encryption.ts b/src/client-side-encryption/client_encryption.ts index 69c5572c4a4..b5968fd0d76 100644 --- a/src/client-side-encryption/client_encryption.ts +++ b/src/client-side-encryption/client_encryption.ts @@ -132,12 +132,9 @@ export class ClientEncryption { this._timeoutMS = timeoutMS; this._credentialProviders = options.credentialProviders; - if ( - options.credentialProviders?.aws && - !isEmptyCredentials('aws', options.kmsProviders || {}) - ) { + if (options.credentialProviders?.aws && !isEmptyCredentials('aws', this._kmsProviders)) { throw new MongoCryptInvalidArgumentError( - 'Cannot provide both a custom credential provider and credentials. Please specify one or the other.' + 'Can only provide a custom AWS credential provider when the state machine is configured for automatic AWS credential fetching' ); } diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index 011bf15d43f..a1ce1642a2a 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -468,7 +468,7 @@ describe('AWS KMS Credential Fetching', function () { this.currentTest?.skipReason && this.skip(); }); - context('when a credential provider is not providered', function () { + context('when a credential provider is not provided', function () { it('KMS credentials are successfully fetched.', async function () { const { aws } = await refreshKMSCredentials({ aws: {} }); @@ -479,20 +479,23 @@ describe('AWS KMS Credential Fetching', function () { context('when a credential provider is provided', function () { let credentialProvider; + let providerCount = 0; beforeEach(function () { // @ts-expect-error We intentionally access a protected variable. - credentialProvider = AWSTemporaryCredentialProvider.awsSDK; + const provider = AWSTemporaryCredentialProvider.awsSDK; + credentialProvider = async () => { + providerCount++; + return await provider.fromNodeProviderChain().apply(); + }; }); it('KMS credentials are successfully fetched.', async function () { - const { aws } = await refreshKMSCredentials( - { aws: {} }, - { aws: credentialProvider.fromNodeProviderChain() } - ); + 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); }); }); 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 75d4e02b884..c57a390610f 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 @@ -18,7 +18,7 @@ const masterKey = { key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0' }; -describe('25. Custom AWS Credential Providers', metadata, () => { +describe('26. Custom AWS Credential Providers', metadata, () => { let keyVaultClient; let credentialProvider; diff --git a/test/integration/client-side-encryption/driver.test.ts b/test/integration/client-side-encryption/driver.test.ts index 7f3161ed65c..2854dd8912e 100644 --- a/test/integration/client-side-encryption/driver.test.ts +++ b/test/integration/client-side-encryption/driver.test.ts @@ -50,67 +50,6 @@ describe('Client Side Encryption Functional', function () { const keyVaultCollName = 'datakeys'; const keyVaultNamespace = `${keyVaultDbName}.${keyVaultCollName}`; - describe('ClientEncryption', metadata, function () { - describe('#constructor', function () { - context('when a custom credential provider and credentials are provided', function () { - let client; - - before(function () { - client = this.configuration.newClient({}); - }); - - it('throws an error', function () { - expect(() => { - new ClientEncryption(client, { - keyVaultNamespace: 'test.keyvault', - kmsProviders: { - aws: { secretAccessKey: 'test', accessKeyId: 'test' } - }, - credentialProviders: { - aws: async () => { - return { - sessionToken: 'test', - secretAccessKey: 'test', - accessKeyId: 'test' - }; - } - } - }); - }).to.throw(/custom credential provider and credentials/); - }); - }); - }); - }); - - describe('AutoEncrypter', metadata, function () { - context('when a custom credential provider and credentials are provided', function () { - it('throws an error', function () { - expect(() => { - this.configuration.newClient( - {}, - { - autoEncryption: { - keyVaultNamespace: 'test.keyvault', - kmsProviders: { - aws: { secretAccessKey: 'test', accessKeyId: 'test' } - }, - credentialProviders: { - aws: async () => { - return { - sessionToken: 'test', - secretAccessKey: 'test', - accessKeyId: 'test' - }; - } - } - } - } - ); - }).to.throw(/custom credential provider and credentials/); - }); - }); - }); - describe('Collection', metadata, function () { describe('#bulkWrite()', metadata, function () { context('when encryption errors', function () { From b52fe67596e96940f515b664773a4e755650e191 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Tue, 11 Mar 2025 23:03:30 +0000 Subject: [PATCH 22/25] fix: tests --- test/integration/auth/mongodb_aws.test.ts | 14 +++++++------- ...rose.26.custom_aws_credential_providers.test.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index a1ce1642a2a..e65b9c60bda 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -144,6 +144,12 @@ describe('MONGODB-AWS', function () { 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.'; + return this.skip(); + } }); it('authenticates with a user provided credentials provider', async function () { @@ -167,13 +173,7 @@ describe('MONGODB-AWS', function () { expect(result).to.not.be.instanceOf(MongoServerError); expect(result).to.be.a('number'); - // 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) { - expect(providerCount).to.equal(0); - } else { - expect(providerCount).to.be.greaterThan(0); - } + expect(providerCount).to.be.greaterThan(0); }); }); 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 c57a390610f..08908d98f9a 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 @@ -53,7 +53,7 @@ describe('26. Custom AWS Credential Providers', metadata, () => { }, credentialProviders: { aws: credentialProvider.fromNodeProviderChain() } }); - }).to.throw(/custom credential provider and credentials/); + }).to.throw(/Can only provide a custom AWS credential provider/); }); } ); @@ -105,7 +105,7 @@ describe('26. Custom AWS Credential Providers', metadata, () => { credentialProviders: { aws: credentialProvider.fromNodeProviderChain() } } }); - }).to.throw(/custom credential provider and credentials/); + }).to.throw(/Can only provide a custom AWS credential provider/); }); } ); From 39cd313078bd8eacb080247d9a9014c4c06b277b Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 12 Mar 2025 00:26:23 +0000 Subject: [PATCH 23/25] fix: pass auth mech props to get providers --- src/mongo_client_auth_providers.ts | 55 +++++++++++++----------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/src/mongo_client_auth_providers.ts b/src/mongo_client_auth_providers.ts index 9e4d6656eb1..54aab957a56 100644 --- a/src/mongo_client_auth_providers.ts +++ b/src/mongo_client_auth_providers.ts @@ -1,5 +1,4 @@ import { type AuthProvider } from './cmap/auth/auth_provider'; -import { type AWSCredentialProvider } from './cmap/auth/aws_temporary_credentials'; import { GSSAPI } from './cmap/auth/gssapi'; import { type AuthMechanismProperties } from './cmap/auth/mongo_credentials'; import { MongoDBAWS } from './cmap/auth/mongodb_aws'; @@ -14,10 +13,13 @@ import { X509 } from './cmap/auth/x509'; import { MongoInvalidArgumentError } from './error'; /** @internal */ -const AUTH_PROVIDERS = new Map AuthProvider>([ +const AUTH_PROVIDERS = new Map< + AuthMechanism | string, + (authMechanismProperties: AuthMechanismProperties) => AuthProvider +>([ [ AuthMechanism.MONGODB_AWS, - (credentialProvider?: AWSCredentialProvider) => new MongoDBAWS(credentialProvider) + ({ AWS_CREDENTIAL_PROVIDER }) => new MongoDBAWS(AWS_CREDENTIAL_PROVIDER) ], [ AuthMechanism.MONGODB_CR, @@ -28,7 +30,7 @@ const AUTH_PROVIDERS = new Map AuthProv } ], [AuthMechanism.MONGODB_GSSAPI, () => new GSSAPI()], - [AuthMechanism.MONGODB_OIDC, (workflow?: Workflow) => new MongoDBOIDC(workflow)], + [AuthMechanism.MONGODB_OIDC, properties => new MongoDBOIDC(getWorkflow(properties))], [AuthMechanism.MONGODB_PLAIN, () => new Plain()], [AuthMechanism.MONGODB_SCRAM_SHA1, () => new ScramSHA1()], [AuthMechanism.MONGODB_SCRAM_SHA256, () => new ScramSHA256()], @@ -66,39 +68,28 @@ export class MongoClientAuthProviders { throw new MongoInvalidArgumentError(`authMechanism ${name} not supported`); } - let provider; - if (name === AuthMechanism.MONGODB_OIDC) { - provider = providerFunction(this.getWorkflow(authMechanismProperties)); - } else if (name === AuthMechanism.MONGODB_AWS) { - provider = providerFunction(authMechanismProperties.AWS_CREDENTIAL_PROVIDER); - } else { - provider = providerFunction(); - } - + const provider = providerFunction(authMechanismProperties); this.existingProviders.set(name, provider); return provider; } +} - /** - * Gets either a device workflow or callback workflow. - */ - getWorkflow(authMechanismProperties: AuthMechanismProperties): Workflow { - if (authMechanismProperties.OIDC_HUMAN_CALLBACK) { - return new HumanCallbackWorkflow( - new TokenCache(), - authMechanismProperties.OIDC_HUMAN_CALLBACK +/** + * Gets either a device workflow or callback workflow. + */ +function getWorkflow(authMechanismProperties: AuthMechanismProperties): Workflow { + if (authMechanismProperties.OIDC_HUMAN_CALLBACK) { + return new HumanCallbackWorkflow(new TokenCache(), authMechanismProperties.OIDC_HUMAN_CALLBACK); + } else if (authMechanismProperties.OIDC_CALLBACK) { + return new AutomatedCallbackWorkflow(new TokenCache(), authMechanismProperties.OIDC_CALLBACK); + } else { + const environment = authMechanismProperties.ENVIRONMENT; + const workflow = OIDC_WORKFLOWS.get(environment)?.(); + if (!workflow) { + throw new MongoInvalidArgumentError( + `Could not load workflow for environment ${authMechanismProperties.ENVIRONMENT}` ); - } else if (authMechanismProperties.OIDC_CALLBACK) { - return new AutomatedCallbackWorkflow(new TokenCache(), authMechanismProperties.OIDC_CALLBACK); - } else { - const environment = authMechanismProperties.ENVIRONMENT; - const workflow = OIDC_WORKFLOWS.get(environment)?.(); - if (!workflow) { - throw new MongoInvalidArgumentError( - `Could not load workflow for environment ${authMechanismProperties.ENVIRONMENT}` - ); - } - return workflow; } + return workflow; } } From 3c8a5db746d99f9d07c728bef197f3ec0163d311 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 12 Mar 2025 22:41:41 +0000 Subject: [PATCH 24/25] test: update names --- ...ryption.prose.26.custom_aws_credential_providers.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 08908d98f9a..da4a90741e3 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 @@ -38,7 +38,7 @@ describe('26. Custom AWS Credential Providers', metadata, () => { }); context( - 'Case 1: Explicit encryption with credentials and custom credential provider', + 'Case 1: ClientEncryption with credentialProviders and incorrect kmsProviders', metadata, function () { it('throws an error', metadata, function () { @@ -58,7 +58,7 @@ describe('26. Custom AWS Credential Providers', metadata, () => { } ); - context('Case 2: Explicit encryption with custom credential provider', metadata, function () { + context('Case 2: ClientEncryption with credentialProviders works', metadata, function () { let clientEncryption; let providerCount = 0; @@ -88,7 +88,7 @@ describe('26. Custom AWS Credential Providers', metadata, () => { }); context( - 'Case 3: Auto encryption with credentials and custom credential provider', + 'Case 3: AutoEncryptionOpts with credentialProviders and incorrect kmsProviders', metadata, function () { it('throws an error', metadata, function () { From 4f73b77a5544e00ec344e135cf302d5aafb686cc Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 13 Mar 2025 23:54:24 +0100 Subject: [PATCH 25/25] chore: add comments --- src/cmap/auth/mongo_credentials.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/cmap/auth/mongo_credentials.ts b/src/cmap/auth/mongo_credentials.ts index 0282d311aa8..259147d5b07 100644 --- a/src/cmap/auth/mongo_credentials.ts +++ b/src/cmap/auth/mongo_credentials.ts @@ -69,7 +69,32 @@ export interface AuthMechanismProperties extends Document { ALLOWED_HOSTS?: string[]; /** The resource token for OIDC auth in Azure and GCP. */ TOKEN_RESOURCE?: string; - /** A custom AWS credential provider to use. */ + /** + * A custom AWS credential provider to use. An example using the AWS SDK default provider chain: + * + * ```ts + * const client = new MongoClient(process.env.MONGODB_URI, { + * authMechanismProperties: { + * AWS_CREDENTIAL_PROVIDER: fromNodeProviderChain() + * } + * }); + * ``` + * + * Using a custom function that returns AWS credentials: + * + * ```ts + * const client = new MongoClient(process.env.MONGODB_URI, { + * authMechanismProperties: { + * AWS_CREDENTIAL_PROVIDER: async () => { + * return { + * accessKeyId: process.env.ACCESS_KEY_ID, + * secretAccessKey: process.env.SECRET_ACCESS_KEY + * } + * } + * } + * }); + * ``` + */ AWS_CREDENTIAL_PROVIDER?: AWSCredentialProvider; }