From 8e87e5c5be04902a75887c968f06325c1c7f0477 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Tue, 21 Mar 2023 12:32:11 -0400 Subject: [PATCH] feat(NODE-5077): automatic Azure kms credential refresh (#3599) --- .evergreen/config.in.yml | 76 ++++++++++ .evergreen/config.yml | 82 ++++++++++- .evergreen/copy-driver-to-azure.sh | 24 ++++ .evergreen/generate_evergreen_tasks.js | 15 +- .evergreen/run-azure-kms-mock-server.sh | 9 ++ .evergreen/run-azure-kms-tests.sh | 20 +++ .evergreen/run-gcp-kms-tests.sh | 2 +- .evergreen/run-serverless-tests.sh | 2 +- .evergreen/run-tests.sh | 2 +- .evergreen/setup-azure-vm.sh | 20 +++ global.d.ts | 1 + src/deps.ts | 3 +- ...ion.prose.18.azure_kms_mock_server.test.ts | 130 ++++++++++++++++++ ...ncryption.prose.19.on_demand_azure.test.ts | 73 ++++++++++ .../runner/filters/idms_mock_server_filter.js | 62 +++++++++ test/types/encryption.test-d.ts | 5 +- 16 files changed, 512 insertions(+), 14 deletions(-) create mode 100644 .evergreen/copy-driver-to-azure.sh create mode 100644 .evergreen/run-azure-kms-mock-server.sh create mode 100644 .evergreen/run-azure-kms-tests.sh create mode 100644 .evergreen/setup-azure-vm.sh create mode 100644 test/integration/client-side-encryption/client_side_encryption.prose.18.azure_kms_mock_server.test.ts create mode 100644 test/integration/client-side-encryption/client_side_encryption.prose.19.on_demand_azure.test.ts create mode 100644 test/tools/runner/filters/idms_mock_server_filter.js diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index 452bbc2fba..21461f2317 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -94,6 +94,15 @@ functions: - .evergreen/run-kms-servers.sh env: DRIVERS_TOOLS: ${DRIVERS_TOOLS} + - command: subprocess.exec + params: + background: true + working_dir: src + binary: bash + args: + - .evergreen/run-azure-kms-mock-server.sh + env: + DRIVERS_TOOLS: ${DRIVERS_TOOLS} "bootstrap oidc": - command: ec2.assume_role @@ -1136,6 +1145,46 @@ tasks: args: - src/.evergreen/run-gcp-kms-tests.sh + + - name: "test-azurekms-task" + commands: + - func: "install dependencies" + - command: subprocess.exec + type: setup + params: + binary: bash + add_expansions_to_env: true + args: + - src/.evergreen/copy-driver-to-azure.sh + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + add_expansions_to_env: true + env: + AZUREKMS_CMD: "env EXPECTED_AZUREKMS_OUTCOME=success bash src/.evergreen/run-azure-kms-tests.sh" + args: + - ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/run-command.sh + + - name: "test-azurekms-fail-task" + commands: + - func: "install dependencies" + - func: bootstrap mongo-orchestration + vars: + VERSION: latest + TOPOLOGY: server + AUTH: noauth + - command: subprocess.exec + type: test + params: + binary: bash + env: + EXPECTED_AZUREKMS_OUTCOME: "failure" + args: + - src/.evergreen/run-azure-kms-tests.sh + + task_groups: - name: serverless_task_group setup_group_can_fail_task: true @@ -1208,6 +1257,33 @@ task_groups: tasks: - test-gcpkms-task + - name: test_azurekms_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - command: subprocess.exec + params: + working_dir: "src" + binary: bash + add_expansions_to_env: true + args: + - .evergreen/setup-azure-vm.sh + - command: expansions.update + # Load AZUREKMS_VMNAME into the expansions. + params: + file: src/testazurekms-expansions.yml + + teardown_group: + - command: subprocess.exec + params: + binary: bash + add_expansions_to_env: true + args: + - ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/delete-vm.sh + tasks: + - test-azurekms-task + pre: - func: "fetch source" - func: "windows fix" diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 0507ff27f9..8ffdfc86cf 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -68,6 +68,15 @@ functions: - .evergreen/run-kms-servers.sh env: DRIVERS_TOOLS: ${DRIVERS_TOOLS} + - command: subprocess.exec + params: + background: true + working_dir: src + binary: bash + args: + - .evergreen/run-azure-kms-mock-server.sh + env: + DRIVERS_TOOLS: ${DRIVERS_TOOLS} bootstrap oidc: - command: ec2.assume_role params: @@ -1067,6 +1076,42 @@ tasks: EXPECTED_GCPKMS_OUTCOME: failure args: - src/.evergreen/run-gcp-kms-tests.sh + - name: test-azurekms-task + commands: + - func: install dependencies + - command: subprocess.exec + type: setup + params: + binary: bash + add_expansions_to_env: true + args: + - src/.evergreen/copy-driver-to-azure.sh + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + add_expansions_to_env: true + env: + AZUREKMS_CMD: env EXPECTED_AZUREKMS_OUTCOME=success bash src/.evergreen/run-azure-kms-tests.sh + args: + - ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/run-command.sh + - name: test-azurekms-fail-task + commands: + - func: install dependencies + - func: bootstrap mongo-orchestration + vars: + VERSION: latest + TOPOLOGY: server + AUTH: noauth + - command: subprocess.exec + type: test + params: + binary: bash + env: + EXPECTED_AZUREKMS_OUTCOME: failure + args: + - src/.evergreen/run-azure-kms-tests.sh - name: test-latest-server tags: - latest @@ -2489,7 +2534,7 @@ tasks: - func: bootstrap kms servers - func: run custom csfle tests vars: - CSFLE_GIT_REF: 77b51c00ab4ff58916dd39f55657e1ecc0af281c + CSFLE_GIT_REF: cd7e938619aa52ce652d13690780df5f383bbef0 - name: run-custom-csfle-tests-5.0-master tags: - run-custom-dependency-tests @@ -2519,7 +2564,7 @@ tasks: - func: bootstrap kms servers - func: run custom csfle tests vars: - CSFLE_GIT_REF: 77b51c00ab4ff58916dd39f55657e1ecc0af281c + CSFLE_GIT_REF: cd7e938619aa52ce652d13690780df5f383bbef0 - name: run-custom-csfle-tests-rapid-master tags: - run-custom-dependency-tests @@ -2549,7 +2594,7 @@ tasks: - func: bootstrap kms servers - func: run custom csfle tests vars: - CSFLE_GIT_REF: 77b51c00ab4ff58916dd39f55657e1ecc0af281c + CSFLE_GIT_REF: cd7e938619aa52ce652d13690780df5f383bbef0 - name: run-custom-csfle-tests-latest-master tags: - run-custom-dependency-tests @@ -3088,6 +3133,30 @@ task_groups: - ${DRIVERS_TOOLS}/.evergreen/csfle/gcpkms/delete-instance.sh tasks: - test-gcpkms-task + - name: test_azurekms_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + setup_group: + - func: fetch source + - command: subprocess.exec + params: + working_dir: src + binary: bash + add_expansions_to_env: true + args: + - .evergreen/setup-azure-vm.sh + - command: expansions.update + params: + file: src/testazurekms-expansions.yml + teardown_group: + - command: subprocess.exec + params: + binary: bash + add_expansions_to_env: true + args: + - ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/delete-vm.sh + tasks: + - test-azurekms-task pre: - func: fetch source - func: windows fix @@ -3557,6 +3626,13 @@ buildvariants: tasks: - test_gcpkms_task_group - test-gcpkms-fail-task + - name: debian11-test-azure-kms + display_name: Azure KMS Test + run_on: debian11-small + batchtime: 20160 + tasks: + - test_azurekms_task_group + - test-azurekms-fail-task - name: rhel8-no-auth-tests display_name: No Auth Tests run_on: rhel80-large diff --git a/.evergreen/copy-driver-to-azure.sh b/.evergreen/copy-driver-to-azure.sh new file mode 100644 index 0000000000..d66605bd4c --- /dev/null +++ b/.evergreen/copy-driver-to-azure.sh @@ -0,0 +1,24 @@ +#! /usr/bin/env bash + +set -o errexit + +if [ -z ${AZUREKMS_RESOURCEGROUP+omitted} ]; then echo "AZUREKMS_RESOURCEGROUP is unset" && exit 1; fi +if [ -z ${AZUREKMS_VMNAME+omitted} ]; then echo "AZUREKMS_VMNAME is unset" && exit 1; fi +if [ -z ${AZUREKMS_PRIVATEKEYPATH+omitted} ]; then echo "AZUREKMS_PRIVATEKEYPATH is unset" && exit 1; fi + +source "${PROJECT_DIRECTORY}/.evergreen/init-nvm.sh" + +echo "compressing node driver source ... begin" +tar -czf node-driver-source.tgz src +echo "compressing node driver source ... end" + +export AZUREKMS_SRC=node-driver-source.tgz +export AZUREKMS_DST="./" +echo "copying node driver tar ... begin" +"${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/copy-file.sh" +echo "copying node driver tar ... end" + +echo "decompressing node driver tar on azure ... begin" +export AZUREKMS_CMD="tar xf node-driver-source.tgz" +"${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/run-command.sh" +echo "decompressing node driver tar on azure ... end" diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 654aba5c76..c10fb27061 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -456,9 +456,8 @@ for (const { BUILD_VARIANTS.push({ name: 'macos-1100', - display_name: `MacOS 11 Node${ - versions.find(version => version.codeName === LATEST_LTS).versionNumber - }`, + display_name: `MacOS 11 Node${versions.find(version => version.codeName === LATEST_LTS).versionNumber + }`, run_on: 'macos-1100', expansions: { NODE_LTS_NAME: LATEST_LTS, @@ -596,7 +595,7 @@ BUILD_VARIANTS.push({ const oneOffFuncAsTasks = []; -const FLE_PINNED_COMMIT = '77b51c00ab4ff58916dd39f55657e1ecc0af281c'; +const FLE_PINNED_COMMIT = 'cd7e938619aa52ce652d13690780df5f383bbef0'; for (const version of ['5.0', 'rapid', 'latest']) { for (const ref of [FLE_PINNED_COMMIT, 'master']) { @@ -668,6 +667,14 @@ BUILD_VARIANTS.push({ tasks: ['test_gcpkms_task_group', 'test-gcpkms-fail-task'] }); +BUILD_VARIANTS.push({ + name: 'debian11-test-azure-kms', + display_name: 'Azure KMS Test', + run_on: 'debian11-small', + batchtime: 20160, + tasks: ['test_azurekms_task_group', 'test-azurekms-fail-task'] +}); + BUILD_VARIANTS.push({ name: 'rhel8-no-auth-tests', display_name: 'No Auth Tests', diff --git a/.evergreen/run-azure-kms-mock-server.sh b/.evergreen/run-azure-kms-mock-server.sh new file mode 100644 index 0000000000..06d07236e9 --- /dev/null +++ b/.evergreen/run-azure-kms-mock-server.sh @@ -0,0 +1,9 @@ +#! /user/bin/env bash + +if [ -z ${DRIVERS_TOOLS+omitted} ]; then echo "DRIVERS_TOOLS is unset" && exit 1; fi + +set -o errexit + +python3 $DRIVERS_TOOLS/.evergreen/csfle/bottle.py fake_azure:imds & + +echo "Running Azure KMS idms server on port 8080" diff --git a/.evergreen/run-azure-kms-tests.sh b/.evergreen/run-azure-kms-tests.sh new file mode 100644 index 0000000000..9e715e1bee --- /dev/null +++ b/.evergreen/run-azure-kms-tests.sh @@ -0,0 +1,20 @@ +#! /usr/bin/env bash + +set -o errexit + +pushd "src" +PROJECT_DIRECTORY="$(pwd)" +export PROJECT_DIRECTORY +source ".evergreen/init-nvm.sh" + +set -o xtrace + +npm install --force 'mongodb-client-encryption@latest' + +export MONGODB_URI="mongodb://localhost:27017" + +export EXPECTED_AZUREKMS_OUTCOME=${EXPECTED_AZUREKMS_OUTCOME:-omitted} +export TEST_CSFLE=true +export CSFLE_KMS_PROVIDERS='not json' + +npx mocha --config test/mocha_mongodb.json test/integration/client-side-encryption/client_side_encryption.prose.19.on_demand_azure.test.ts diff --git a/.evergreen/run-gcp-kms-tests.sh b/.evergreen/run-gcp-kms-tests.sh index 86558b9ec4..fe77ea1dfd 100644 --- a/.evergreen/run-gcp-kms-tests.sh +++ b/.evergreen/run-gcp-kms-tests.sh @@ -9,7 +9,7 @@ source ".evergreen/init-nvm.sh" set -o xtrace -npm install 'mongodb-client-encryption@2.6.0' +npm install 'mongodb-client-encryption@latest' npm install 'gcp-metadata' export MONGODB_URI="mongodb://localhost:27017" diff --git a/.evergreen/run-serverless-tests.sh b/.evergreen/run-serverless-tests.sh index cd6fb06ffa..c226c5b707 100755 --- a/.evergreen/run-serverless-tests.sh +++ b/.evergreen/run-serverless-tests.sh @@ -10,7 +10,7 @@ if [ -z ${MONGODB_URI+omitted} ]; then echo "MONGODB_URI is unset" && exit 1; fi if [ -z ${SERVERLESS_ATLAS_USER+omitted} ]; then echo "SERVERLESS_ATLAS_USER is unset" && exit 1; fi if [ -z ${SERVERLESS_ATLAS_PASSWORD+omitted} ]; then echo "SERVERLESS_ATLAS_PASSWORD is unset" && exit 1; fi -npm install mongodb-client-encryption@"2.6.0" +npm install 'mongodb-client-encryption@latest' npx mocha \ --config test/mocha_mongodb.json \ diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 95c9750f86..12fd10ffc1 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -52,7 +52,7 @@ else source "$DRIVERS_TOOLS"/.evergreen/csfle/set-temp-creds.sh fi -npm install mongodb-client-encryption@"2.6.0" +npm install 'mongodb-client-encryption@latest' npm install @mongodb-js/zstd npm install snappy diff --git a/.evergreen/setup-azure-vm.sh b/.evergreen/setup-azure-vm.sh new file mode 100644 index 0000000000..a1902849be --- /dev/null +++ b/.evergreen/setup-azure-vm.sh @@ -0,0 +1,20 @@ +#! /usr/bin/env bash + +echo "${testazurekms_publickey}" > /tmp/testazurekms_publickey +echo "${testazurekms_privatekey}" > /tmp/testazurekms_privatekey + +# Set 600 permissions on private key file. Otherwise ssh / scp may error with permissions "are too open". +chmod 600 /tmp/testazurekms_privatekey +export AZUREKMS_CLIENTID=${AZUREKMS_CLIENTID} +export AZUREKMS_TENANTID=${AZUREKMS_TENANTID} +export AZUREKMS_SECRET=${AZUREKMS_SECRET} +export AZUREKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS +export AZUREKMS_RESOURCEGROUP=${AZUREKMS_RESOURCEGROUP} +export AZUREKMS_PUBLICKEYPATH=/tmp/testazurekms_publickey +export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey +export AZUREKMS_SCOPE=${AZUREKMS_SCOPE} +export AZUREKMS_VMNAME_PREFIX=NODEDRIVER + +$DRIVERS_TOOLS/.evergreen/csfle/azurekms/create-and-setup-vm.sh + +echo "AZUREKMS_PRIVATEKEYPATH: /tmp/testazurekms_privatekey" >> testazurekms-expansions.yml diff --git a/global.d.ts b/global.d.ts index 83c66eace0..010e123336 100644 --- a/global.d.ts +++ b/global.d.ts @@ -11,6 +11,7 @@ declare global { clientSideEncryption?: boolean; serverless?: 'forbid' | 'allow' | 'require'; auth?: 'enabled' | 'disabled'; + idmsMockServer?: true; }; sessions?: { diff --git a/src/deps.ts b/src/deps.ts index 1100a1c179..6c4857270a 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -266,7 +266,8 @@ export interface AutoEncryptionOptions { * If present, an access token to authenticate with Azure. */ accessToken: string; - }; + } + | Record; /** Configuration options for using 'gcp' as your KMS provider */ gcp?: | { diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.18.azure_kms_mock_server.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.18.azure_kms_mock_server.test.ts new file mode 100644 index 0000000000..69b4c8aa59 --- /dev/null +++ b/test/integration/client-side-encryption/client_side_encryption.prose.18.azure_kms_mock_server.test.ts @@ -0,0 +1,130 @@ +import { expect } from 'chai'; + +import { Document } from '../../mongodb'; + +const BASE_URL = new URL(`http://127.0.0.1:8080/metadata/identity/oauth2/token`); +class KMSRequestOptions { + url: URL = BASE_URL; + headers: Document; + constructor(testCase?: 'empty-json' | 'bad-json' | '404' | '500' | 'slow') { + this.headers = + testCase != null + ? { + 'X-MongoDB-HTTP-TestParams': `case=${testCase}` + } + : {}; + } +} + +const metadata: MongoDBMetadataUI = { + requires: { + clientSideEncryption: true, + idmsMockServer: true + } +}; + +context('Azure KMS Mock Server Tests', function () { + let fetchAzureKMSToken: (options: { + url: URL; + headers: Document; + }) => Promise<{ accessToken: string }>; + let MongoCryptAzureKMSRequestError; + + beforeEach(async function () { + fetchAzureKMSToken = this.configuration.mongodbClientEncryption['___azureKMSProseTestExports']; + MongoCryptAzureKMSRequestError = + this.configuration.mongodbClientEncryption.MongoCryptAzureKMSRequestError; + }); + + context('Case 1: Success', metadata, function () { + // Do not set an ``X-MongoDB-HTTP-TestParams`` header. + + // Upon receiving a response from ``fake_azure``, the driver must decode the + // following information: + + // 1. HTTP status will be ``200 Okay``. + // 2. The HTTP body will be a valid JSON string. + // 3. The access token will be the string ``"magic-cookie"``. + // 4. The expiry duration of the token will be seventy seconds. + // 5. The token will have a resource of ``"https://vault.azure.net"`` + + it('returns a properly formatted access token', async () => { + const credentials = await fetchAzureKMSToken(new KMSRequestOptions()); + expect(credentials).to.have.property('accessToken', 'magic-cookie'); + }); + }); + + context('Case 2: Empty JSON', metadata, function () { + // This case addresses a server returning valid JSON with invalid content. + // Set ``X-MongoDB-HTTP-TestParams`` to ``case=empty-json``. + // Upon receiving a response: + // 1. HTTP status will be ``200 Okay`` + // 2. The HTTP body will be a valid JSON string. + // 3. There will be no access token, expiry duration, or resource. + // The test case should ensure that this error condition is handled gracefully. + + it('returns an error', async () => { + const error = await fetchAzureKMSToken(new KMSRequestOptions('empty-json')).catch(e => e); + + expect(error).to.be.instanceof(MongoCryptAzureKMSRequestError); + }); + }); + + context('Case 3: Bad JSON', metadata, function () { + // This case addresses a server returning malformed JSON. + // Set ``X-MongoDB-HTTP-TestParams`` to ``case=bad-json``. + // Upon receiving a response: + // 1. HTTP status will be ``200 Okay`` + // 2. The response body will contain a malformed JSON string. + // The test case should ensure that this error condition is handled gracefully. + + it('returns an error', async () => { + const error = await fetchAzureKMSToken(new KMSRequestOptions('bad-json')).catch(e => e); + + expect(error).to.be.instanceof(MongoCryptAzureKMSRequestError); + }); + }); + + context('Case 4: HTTP 404', metadata, function () { + // This case addresses a server returning a "Not Found" response. This is + // documented to occur spuriously within an Azure environment. + // Set ``X-MongoDB-HTTP-TestParams`` to ``case=404``. + // Upon receiving a response: + // 1. HTTP status will be ``404 Not Found``. + // 2. The response body is unspecified. + // The test case should ensure that this error condition is handled gracefully. + it('returns an error', async () => { + const error = await fetchAzureKMSToken(new KMSRequestOptions('404')).catch(e => e); + + expect(error).to.be.instanceof(MongoCryptAzureKMSRequestError); + }); + }); + + context('Case 5: HTTP 500', metadata, function () { + // This case addresses an IMDS server reporting an internal error. This is + // documented to occur spuriously within an Azure environment. + // Set ``X-MongoDB-HTTP-TestParams`` to ``case=500``. + // Upon receiving a response: + // 1. HTTP status code will be ``500``. + // 2. The response body is unspecified. + // The test case should ensure that this error condition is handled gracefully. + it('returns an error', async () => { + const error = await fetchAzureKMSToken(new KMSRequestOptions('500')).catch(e => e); + + expect(error).to.be.instanceof(MongoCryptAzureKMSRequestError); + }); + }); + + context('Case 6: Slow Response', metadata, function () { + // This case addresses an IMDS server responding very slowly. Drivers should not + // halt the application waiting on a peer to communicate. + // Set ``X-MongoDB-HTTP-TestParams`` to ``case=slow``. + // The HTTP response from the ``fake_azure`` server will take at least 1000 seconds + // to complete. The request should fail with a timeout. + it('returns an error after the request times out', async () => { + const error = await fetchAzureKMSToken(new KMSRequestOptions('slow')).catch(e => e); + + expect(error).to.be.instanceof(MongoCryptAzureKMSRequestError); + }); + }); +}); diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.19.on_demand_azure.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.19.on_demand_azure.test.ts new file mode 100644 index 0000000000..4c70172f2e --- /dev/null +++ b/test/integration/client-side-encryption/client_side_encryption.prose.19.on_demand_azure.test.ts @@ -0,0 +1,73 @@ +import { expect } from 'chai'; +import { env } from 'process'; + +import { Binary } from '../../mongodb'; + +const metadata: MongoDBMetadataUI = { + requires: { + clientSideEncryption: true + } +} as const; + +const dataKeyOptions = { + masterKey: { + keyVaultEndpoint: 'https://keyvault-drivers-2411.vault.azure.net/keys/', + keyName: 'KEY-NAME' + } +}; + +describe('19. On-demand Azure Credentials', () => { + let clientEncryption: import('mongodb-client-encryption').ClientEncryption; + let keyVaultClient; + let MongoCryptAzureKMSRequestError; + + beforeEach(async function () { + keyVaultClient = this.configuration.newClient(); + + const { ClientEncryption } = this.configuration.mongodbClientEncryption; + MongoCryptAzureKMSRequestError = + this.configuration.mongodbClientEncryption.MongoCryptAzureKMSRequestError; + + if (typeof env.AZUREKMS_VMNAME === 'string') { + // If azure cloud env is present then EXPECTED_AZUREKMS_OUTCOME MUST be set + expect( + env.EXPECTED_AZUREKMS_OUTCOME, + `EXPECTED_AZUREKMS_OUTCOME must be 'success' or 'failure'` + ) + .to.be.a('string') + .that.satisfies(s => s === 'success' || s === 'failure'); + } + + clientEncryption = new ClientEncryption(keyVaultClient, { + keyVaultClient, + keyVaultNamespace: 'keyvault.datakeys', + kmsProviders: { azure: {} } + }); + }); + + afterEach(async () => { + await keyVaultClient?.close(); + }); + + it('Case 1: Failure', metadata, async function () { + if (env.EXPECTED_AZUREKMS_OUTCOME !== 'failure') { + this.skipReason = 'This test is supposed to run in the environment where failure is expected'; + this.skip(); + } + + const error = await clientEncryption + .createDataKey('azure', dataKeyOptions) + .catch(error => error); + expect(error).to.be.instanceOf(MongoCryptAzureKMSRequestError); + }); + + it('Case 2: Success', metadata, async function () { + if (env.EXPECTED_AZUREKMS_OUTCOME !== 'success') { + this.skipReason = 'This test is supposed to run in the environment where success is expected'; + this.skip(); + } + + const dk = await clientEncryption.createDataKey('azure', dataKeyOptions); + expect(dk).to.be.instanceOf(Binary); + }); +}); diff --git a/test/tools/runner/filters/idms_mock_server_filter.js b/test/tools/runner/filters/idms_mock_server_filter.js new file mode 100644 index 0000000000..0499854189 --- /dev/null +++ b/test/tools/runner/filters/idms_mock_server_filter.js @@ -0,0 +1,62 @@ +'use strict'; + +const { get } = require('http'); + +async function isMockServerSetup() { + const url = (() => { + const url = new URL(`http://127.0.0.1:8080/metadata/identity/oauth2/token`); + + // minimum configuration for the mock server not to throw an error when responding. + url.searchParams.append('api-version', '2018-02-01'); + url.searchParams.append('resource', 'https://vault.azure.net'); + return url; + })(); + return new Promise((resolve, reject) => { + get(url, res => { + if (res.statusCode === 200) { + return resolve(); + } + return reject('server not running'); + }) + .on('error', error => reject(error)) + .end(); + }); +} + +/** + * Filter for tests that require the mock idms server to be running. + * + * example: + * metadata: { + * requires: { + * idmsMockServer: true + * } + * } + */ +class IDMSMockServerFilter { + initializeFilter(client, context, callback) { + isMockServerSetup() + .then( + () => (this.isRunning = true), + () => (this.isRunning = false) + ) + .then(() => callback()); + } + + filter(test) { + if (!test.metadata) return true; + if (!test.metadata.requires) return true; + if (!test.metadata.requires.idmsMockServer) return true; + + const requiresMockServer = test.metadata.requires.idmsMockServer; + if (!requiresMockServer) { + return true; + } + if (process.env.TEST_CSFLE && !this.isRunning) { + throw new Error('Expected Azure KMS server to be running.'); + } + return this.isRunning; + } +} + +module.exports = IDMSMockServerFilter; diff --git a/test/types/encryption.test-d.ts b/test/types/encryption.test-d.ts index 390b9307f2..a22400e619 100644 --- a/test/types/encryption.test-d.ts +++ b/test/types/encryption.test-d.ts @@ -1,4 +1,4 @@ -import { expectAssignable, expectNotAssignable } from 'tsd'; +import { expectAssignable } from 'tsd'; import type { AutoEncryptionOptions } from '../mongodb'; @@ -10,8 +10,7 @@ expectAssignable({ } }); -// TODO(NODE-4537): Azure support -expectNotAssignable({ +expectAssignable({ kmsProviders: { azure: {} }