Skip to content

Commit b3482f3

Browse files
authored
feat(NODE-4929): Add OIDC Azure workflow (#3670)
1 parent 9561f32 commit b3482f3

30 files changed

+763
-105
lines changed

.evergreen/ci_matrix_constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const DEFAULT_OS = 'rhel80-large';
1717
const WINDOWS_OS = 'windows-vsCurrent-large';
1818
const MACOS_OS = 'macos-1100';
1919
const UBUNTU_OS = 'ubuntu1804-large';
20+
const UBUNTU_20_OS = 'ubuntu2004-small'
2021
const DEBIAN_OS = 'debian11-small';
2122

2223
module.exports = {
@@ -32,5 +33,6 @@ module.exports = {
3233
WINDOWS_OS,
3334
MACOS_OS,
3435
UBUNTU_OS,
36+
UBUNTU_20_OS,
3537
DEBIAN_OS
3638
};

.evergreen/config.in.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,20 @@ tasks:
12441244
args:
12451245
- src/.evergreen/run-azure-kms-tests.sh
12461246

1247+
- name: "oidc-auth-test-azure-latest"
1248+
commands:
1249+
- func: "install dependencies"
1250+
- command: subprocess.exec
1251+
params:
1252+
working_dir: src
1253+
binary: bash
1254+
env:
1255+
DRIVERS_TOOLS: ${DRIVERS_TOOLS}
1256+
PROJECT_DIRECTORY: ${PROJECT_DIRECTORY}
1257+
AZUREOIDC_CLIENTID: ${testazureoidc_clientid}
1258+
PROVIDER_NAME: azure
1259+
args:
1260+
- .evergreen/run-oidc-tests-azure.sh
12471261

12481262
task_groups:
12491263
- name: serverless_task_group
@@ -1348,6 +1362,34 @@ task_groups:
13481362
tasks:
13491363
- test-azurekms-task
13501364

1365+
- name: testazureoidc_task_group
1366+
setup_group:
1367+
- func: fetch source
1368+
- command: shell.exec
1369+
params:
1370+
shell: bash
1371+
script: |-
1372+
set -o errexit
1373+
${PREPARE_SHELL}
1374+
export AZUREOIDC_CLIENTID="${testazureoidc_clientid}"
1375+
export AZUREOIDC_TENANTID="${testazureoic_tenantid}"
1376+
export AZUREOIDC_SECRET="${testazureoidc_secret}"
1377+
export AZUREOIDC_KEYVAULT=${testazureoidc_keyvault}
1378+
export AZUREOIDC_DRIVERS_TOOLS="$DRIVERS_TOOLS"
1379+
export AZUREOIDC_VMNAME_PREFIX="NODE_DRIVER"
1380+
$DRIVERS_TOOLS/.evergreen/auth_oidc/azure/create-and-setup-vm.sh
1381+
teardown_group:
1382+
- command: shell.exec
1383+
params:
1384+
shell: bash
1385+
script: |-
1386+
${PREPARE_SHELL}
1387+
$DRIVERS_TOOLS/.evergreen/auth_oidc/azure/delete-vm.sh
1388+
setup_group_can_fail_task: true
1389+
setup_group_timeout_secs: 1800
1390+
tasks:
1391+
- oidc-auth-test-azure-latest
1392+
13511393
pre:
13521394
- func: "fetch source"
13531395
- func: "windows fix"

.evergreen/config.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,20 @@ tasks:
11681168
EXPECTED_AZUREKMS_OUTCOME: failure
11691169
args:
11701170
- src/.evergreen/run-azure-kms-tests.sh
1171+
- name: oidc-auth-test-azure-latest
1172+
commands:
1173+
- func: install dependencies
1174+
- command: subprocess.exec
1175+
params:
1176+
working_dir: src
1177+
binary: bash
1178+
env:
1179+
DRIVERS_TOOLS: ${DRIVERS_TOOLS}
1180+
PROJECT_DIRECTORY: ${PROJECT_DIRECTORY}
1181+
AZUREOIDC_CLIENTID: ${testazureoidc_clientid}
1182+
PROVIDER_NAME: azure
1183+
args:
1184+
- .evergreen/run-oidc-tests-azure.sh
11711185
- name: test-latest-server
11721186
tags:
11731187
- latest
@@ -3420,6 +3434,33 @@ task_groups:
34203434
- ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/delete-vm.sh
34213435
tasks:
34223436
- test-azurekms-task
3437+
- name: testazureoidc_task_group
3438+
setup_group:
3439+
- func: fetch source
3440+
- command: shell.exec
3441+
params:
3442+
shell: bash
3443+
script: |-
3444+
set -o errexit
3445+
${PREPARE_SHELL}
3446+
export AZUREOIDC_CLIENTID="${testazureoidc_clientid}"
3447+
export AZUREOIDC_TENANTID="${testazureoic_tenantid}"
3448+
export AZUREOIDC_SECRET="${testazureoidc_secret}"
3449+
export AZUREOIDC_KEYVAULT=${testazureoidc_keyvault}
3450+
export AZUREOIDC_DRIVERS_TOOLS="$DRIVERS_TOOLS"
3451+
export AZUREOIDC_VMNAME_PREFIX="NODE_DRIVER"
3452+
$DRIVERS_TOOLS/.evergreen/auth_oidc/azure/create-and-setup-vm.sh
3453+
teardown_group:
3454+
- command: shell.exec
3455+
params:
3456+
shell: bash
3457+
script: |-
3458+
${PREPARE_SHELL}
3459+
$DRIVERS_TOOLS/.evergreen/auth_oidc/azure/delete-vm.sh
3460+
setup_group_can_fail_task: true
3461+
setup_group_timeout_secs: 1800
3462+
tasks:
3463+
- oidc-auth-test-azure-latest
34233464
pre:
34243465
- func: fetch source
34253466
- func: windows fix
@@ -3999,6 +4040,12 @@ buildvariants:
39994040
tasks:
40004041
- test_azurekms_task_group
40014042
- test-azurekms-fail-task
4043+
- name: ubuntu20-test-azure-oidc
4044+
display_name: Azure OIDC
4045+
run_on: ubuntu2004-small
4046+
batchtime: 20160
4047+
tasks:
4048+
- testazureoidc_task_group
40024049
- name: rhel8-no-auth-tests
40034050
display_name: No Auth Tests
40044051
run_on: rhel80-large

.evergreen/generate_evergreen_tasks.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
WINDOWS_OS,
1717
MACOS_OS,
1818
UBUNTU_OS,
19+
UBUNTU_20_OS,
1920
DEBIAN_OS
2021
} = require('./ci_matrix_constants');
2122

@@ -754,6 +755,14 @@ BUILD_VARIANTS.push({
754755
tasks: ['test_azurekms_task_group', 'test-azurekms-fail-task']
755756
});
756757

758+
BUILD_VARIANTS.push({
759+
name: 'ubuntu20-test-azure-oidc',
760+
display_name: 'Azure OIDC',
761+
run_on: UBUNTU_20_OS,
762+
batchtime: 20160,
763+
tasks: ['testazureoidc_task_group']
764+
});
765+
757766
BUILD_VARIANTS.push({
758767
name: 'rhel8-no-auth-tests',
759768
display_name: 'No Auth Tests',

.evergreen/run-oidc-tests-azure.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
set -o xtrace # Write all commands first to stderr
3+
set -o errexit # Exit the script with error if any of the commands fail
4+
5+
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/node-mongodb-native.tgz
6+
tar czf $AZUREOIDC_DRIVERS_TAR_FILE .
7+
export AZUREOIDC_TEST_CMD="source ./env.sh && PROVIDER_NAME=azure ./.evergreen/run-oidc-tests.sh"
8+
export AZUREOIDC_CLIENTID=$AZUREOIDC_CLIENTID
9+
export PROJECT_DIRECTORY=$PROJECT_DIRECTORY
10+
export PROVIDER_NAME=$PROVIDER_NAME
11+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh

.evergreen/run-oidc-tests.sh

100644100755
Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,34 @@
22
set -o errexit # Exit the script with error if any of the commands fail
33
set -o xtrace # Write all commands first to stderr
44

5+
PROVIDER_NAME=${PROVIDER_NAME:-"aws"}
6+
PROJECT_DIRECTORY=${PROJECT_DIRECTORY:-"."}
57
source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh"
68

79
MONGODB_URI=${MONGODB_URI:-"mongodb://127.0.0.1:27017"}
8-
MONGODB_URI_SINGLE="${MONGODB_URI}/?authMechanism=MONGODB-OIDC&authMechanismProperties=DEVICE_NAME:aws"
910

10-
echo $MONGODB_URI_SINGLE
11-
12-
export MONGODB_URI="$MONGODB_URI_SINGLE"
1311
export OIDC_TOKEN_DIR=${OIDC_TOKEN_DIR}
1412

15-
npm run check:oidc
13+
export MONGODB_URI=${MONGODB_URI:-"mongodb://localhost"}
14+
15+
if [ "$PROVIDER_NAME" = "aws" ]; then
16+
export MONGODB_URI_SINGLE="${MONGODB_URI}/?authMechanism=MONGODB-OIDC"
17+
export MONGODB_URI_MULTIPLE="${MONGODB_URI}:27018/?authMechanism=MONGODB-OIDC&directConnection=true"
18+
19+
if [ -z "${OIDC_TOKEN_DIR}" ]; then
20+
echo "Must specify OIDC_TOKEN_DIR"
21+
exit 1
22+
fi
23+
npm run check:oidc
24+
elif [ "$PROVIDER_NAME" = "azure" ]; then
25+
if [ -z "${AZUREOIDC_CLIENTID}" ]; then
26+
echo "Must specify an AZUREOIDC_CLIENTID"
27+
exit 1
28+
fi
29+
MONGODB_URI="${MONGODB_URI}/?authMechanism=MONGODB-OIDC"
30+
MONGODB_URI="${MONGODB_URI}&authMechanismProperties=PROVIDER_NAME:azure"
31+
export MONGODB_URI="${MONGODB_URI},TOKEN_AUDIENCE:api%3A%2F%2F${AZUREOIDC_CLIENTID}"
32+
npm run check:oidc-azure
33+
else
34+
npm run check:oidc
35+
fi

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
"check:adl": "mocha --config test/mocha_mongodb.json test/manual/atlas-data-lake-testing",
138138
"check:aws": "nyc mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_aws.test.ts",
139139
"check:oidc": "mocha --config test/mocha_mongodb.json test/manual/mongodb_oidc.prose.test.ts",
140+
"check:oidc-azure": "mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_oidc_azure.prose.test.ts",
140141
"check:ocsp": "mocha --config test/manual/mocharc.json test/manual/ocsp_support.test.js",
141142
"check:kerberos": "nyc mocha --config test/manual/mocharc.json test/manual/kerberos.test.ts",
142143
"check:tls": "mocha --config test/manual/mocharc.json test/manual/tls_support.test.js",

src/cmap/auth/mongo_credentials.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Resolves the default auth mechanism according to
2+
// Resolves the default auth mechanism according to
23
import type { Document } from '../../bson';
34
import {
45
MongoAPIError,
6+
MongoAzureError,
57
MongoInvalidArgumentError,
68
MongoMissingCredentialsError
79
} from '../../error';
@@ -30,6 +32,7 @@ function getDefaultAuthMechanism(hello?: Document): AuthMechanism {
3032
return AuthMechanism.MONGODB_CR;
3133
}
3234

35+
const ALLOWED_PROVIDER_NAMES: AuthMechanismProperties['PROVIDER_NAME'][] = ['aws', 'azure'];
3336
const ALLOWED_HOSTS_ERROR = 'Auth mechanism property ALLOWED_HOSTS must be an array of strings.';
3437

3538
/** @internal */
@@ -42,6 +45,10 @@ export const DEFAULT_ALLOWED_HOSTS = [
4245
'::1'
4346
];
4447

48+
/** Error for when the token audience is missing in the environment. */
49+
const TOKEN_AUDIENCE_MISSING_ERROR =
50+
'TOKEN_AUDIENCE must be set in the auth mechanism properties when PROVIDER_NAME is azure.';
51+
4552
/** @public */
4653
export interface AuthMechanismProperties extends Document {
4754
SERVICE_HOST?: string;
@@ -54,9 +61,11 @@ export interface AuthMechanismProperties extends Document {
5461
/** @experimental */
5562
REFRESH_TOKEN_CALLBACK?: OIDCRefreshFunction;
5663
/** @experimental */
57-
PROVIDER_NAME?: 'aws';
64+
PROVIDER_NAME?: 'aws' | 'azure';
5865
/** @experimental */
5966
ALLOWED_HOSTS?: string[];
67+
/** @experimental */
68+
TOKEN_AUDIENCE?: string;
6069
}
6170

6271
/** @public */
@@ -176,12 +185,21 @@ export class MongoCredentials {
176185
);
177186
}
178187

188+
if (
189+
this.mechanismProperties.PROVIDER_NAME === 'azure' &&
190+
!this.mechanismProperties.TOKEN_AUDIENCE
191+
) {
192+
throw new MongoAzureError(TOKEN_AUDIENCE_MISSING_ERROR);
193+
}
194+
179195
if (
180196
this.mechanismProperties.PROVIDER_NAME &&
181-
this.mechanismProperties.PROVIDER_NAME !== 'aws'
197+
!ALLOWED_PROVIDER_NAMES.includes(this.mechanismProperties.PROVIDER_NAME)
182198
) {
183199
throw new MongoInvalidArgumentError(
184-
`Currently only a PROVIDER_NAME of 'aws' is supported for mechanism '${this.mechanism}'.`
200+
`Currently only a PROVIDER_NAME in ${ALLOWED_PROVIDER_NAMES.join(
201+
','
202+
)} is supported for mechanism '${this.mechanism}'.`
185203
);
186204
}
187205

src/cmap/auth/mongodb_aws.ts

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import * as crypto from 'crypto';
2-
import * as http from 'http';
3-
import * as url from 'url';
42
import { promisify } from 'util';
53

64
import type { Binary, BSONSerializeOptions } from '../../bson';
@@ -12,7 +10,7 @@ import {
1210
MongoMissingCredentialsError,
1311
MongoRuntimeError
1412
} from '../../error';
15-
import { ByteUtils, maxWireVersion, ns } from '../../utils';
13+
import { ByteUtils, maxWireVersion, ns, request } from '../../utils';
1614
import { type AuthContext, AuthProvider } from './auth_provider';
1715
import { MongoCredentials } from './mongo_credentials';
1816
import { AuthMechanism } from './providers';
@@ -253,61 +251,3 @@ function deriveRegion(host: string) {
253251

254252
return parts[1];
255253
}
256-
257-
interface RequestOptions {
258-
json?: boolean;
259-
method?: string;
260-
timeout?: number;
261-
headers?: http.OutgoingHttpHeaders;
262-
}
263-
264-
async function request(uri: string): Promise<Record<string, any>>;
265-
async function request(
266-
uri: string,
267-
options?: { json?: true } & RequestOptions
268-
): Promise<Record<string, any>>;
269-
async function request(uri: string, options?: { json: false } & RequestOptions): Promise<string>;
270-
async function request(
271-
uri: string,
272-
options: RequestOptions = {}
273-
): Promise<string | Record<string, any>> {
274-
return new Promise<string | Record<string, any>>((resolve, reject) => {
275-
const requestOptions = {
276-
method: 'GET',
277-
timeout: 10000,
278-
json: true,
279-
...url.parse(uri),
280-
...options
281-
};
282-
283-
const req = http.request(requestOptions, res => {
284-
res.setEncoding('utf8');
285-
286-
let data = '';
287-
res.on('data', d => {
288-
data += d;
289-
});
290-
291-
res.once('end', () => {
292-
if (options.json === false) {
293-
resolve(data);
294-
return;
295-
}
296-
297-
try {
298-
const parsed = JSON.parse(data);
299-
resolve(parsed);
300-
} catch {
301-
// TODO(NODE-3483)
302-
reject(new MongoRuntimeError(`Invalid JSON response: "${data}"`));
303-
}
304-
});
305-
});
306-
307-
req.once('timeout', () =>
308-
req.destroy(new MongoAWSError(`AWS request to ${uri} timed out after ${options.timeout} ms`))
309-
);
310-
req.once('error', error => reject(error));
311-
req.end();
312-
});
313-
}

src/cmap/auth/mongodb_oidc.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Connection } from '../connection';
66
import { type AuthContext, AuthProvider } from './auth_provider';
77
import type { MongoCredentials } from './mongo_credentials';
88
import { AwsServiceWorkflow } from './mongodb_oidc/aws_service_workflow';
9+
import { AzureServiceWorkflow } from './mongodb_oidc/azure_service_workflow';
910
import { CallbackWorkflow } from './mongodb_oidc/callback_workflow';
1011

1112
/** Error when credentials are missing. */
@@ -60,7 +61,7 @@ export type OIDCRefreshFunction = (
6061
context: OIDCCallbackContext
6162
) => Promise<IdPServerResponse>;
6263

63-
type ProviderName = 'aws' | 'callback';
64+
type ProviderName = 'aws' | 'azure' | 'callback';
6465

6566
export interface Workflow {
6667
/**
@@ -84,6 +85,7 @@ export interface Workflow {
8485
export const OIDC_WORKFLOWS: Map<ProviderName, Workflow> = new Map();
8586
OIDC_WORKFLOWS.set('callback', new CallbackWorkflow());
8687
OIDC_WORKFLOWS.set('aws', new AwsServiceWorkflow());
88+
OIDC_WORKFLOWS.set('azure', new AzureServiceWorkflow());
8789

8890
/**
8991
* OIDC auth provider.

0 commit comments

Comments
 (0)