From 82c931c704d30f1a9555519d1ac2c9e03a2e69c4 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 7 Nov 2024 15:55:30 +0100 Subject: [PATCH] feat(NODE-6069): OIDC k8s machine workflow (#4270) --- .evergreen/config.in.yml | 120 +++++++++++++++++ .evergreen/config.yml | 126 ++++++++++++++++++ .evergreen/generate_evergreen_tasks.js | 5 +- .evergreen/run-oidc-prose-tests.sh | 8 +- .evergreen/run-oidc-tests-k8s.sh | 14 ++ package.json | 1 + src/cmap/auth/mongo_credentials.ts | 5 +- src/cmap/auth/mongodb_oidc.ts | 4 +- .../auth/mongodb_oidc/k8s_machine_workflow.ts | 38 ++++++ .../auth/mongodb_oidc_k8s.prose.07.test.ts | 38 ++++++ test/spec/auth/legacy/connection-string.json | 20 +++ test/spec/auth/legacy/connection-string.yml | 15 +++ 12 files changed, 389 insertions(+), 5 deletions(-) create mode 100644 .evergreen/run-oidc-tests-k8s.sh create mode 100644 src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts create mode 100644 test/integration/auth/mongodb_oidc_k8s.prose.07.test.ts diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index 45e10f5f66f..141e6be2024 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -1265,6 +1265,60 @@ tasks: args: - src/.evergreen/run-azure-kms-tests.sh + - name: "oidc-auth-test-k8s-latest-eks" + commands: + - func: "install dependencies" + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + env: + DRIVERS_TOOLS: ${DRIVERS_TOOLS} + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + ENVIRONMENT: k8s + K8S_VARIANT: eks + SCRIPT: run-oidc-prose-tests.sh + include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] + args: + - .evergreen/run-oidc-tests-k8s.sh + + - name: "oidc-auth-test-k8s-latest-gke" + commands: + - func: "install dependencies" + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + env: + DRIVERS_TOOLS: ${DRIVERS_TOOLS} + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + ENVIRONMENT: k8s + K8S_VARIANT: gke + SCRIPT: run-oidc-prose-tests.sh + include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] + args: + - .evergreen/run-oidc-tests-k8s.sh + + - name: "oidc-auth-test-k8s-latest-aks" + commands: + - func: "install dependencies" + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + env: + DRIVERS_TOOLS: ${DRIVERS_TOOLS} + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + ENVIRONMENT: k8s + K8S_VARIANT: aks + SCRIPT: run-oidc-prose-tests.sh + include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] + args: + - .evergreen/run-oidc-tests-k8s.sh + - name: "oidc-auth-test-azure-latest" commands: - func: "install dependencies" @@ -1475,6 +1529,72 @@ task_groups: tasks: - test-azurekms-task + - name: testk8soidc_task_group_eks + setup_group: + - func: fetch source + - command: ec2.assume_role + params: + role_arn: ${OIDC_AWS_ROLE_ARN} + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-k8s-latest-eks + + - name: testk8soidc_task_group_gke + setup_group: + - func: fetch source + - command: ec2.assume_role + params: + role_arn: ${OIDC_AWS_ROLE_ARN} + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-k8s-latest-gke + + - name: testk8soidc_task_group_aks + setup_group: + - func: fetch source + - command: ec2.assume_role + params: + role_arn: ${OIDC_AWS_ROLE_ARN} + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-k8s-latest-aks + - name: testtestoidc_task_group setup_group: - func: fetch source diff --git a/.evergreen/config.yml b/.evergreen/config.yml index b99fcdef22a..02eec1dc349 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1217,6 +1217,66 @@ tasks: EXPECTED_AZUREKMS_OUTCOME: failure args: - src/.evergreen/run-azure-kms-tests.sh + - name: oidc-auth-test-k8s-latest-eks + commands: + - func: install dependencies + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + env: + DRIVERS_TOOLS: ${DRIVERS_TOOLS} + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + ENVIRONMENT: k8s + K8S_VARIANT: eks + SCRIPT: run-oidc-prose-tests.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + args: + - .evergreen/run-oidc-tests-k8s.sh + - name: oidc-auth-test-k8s-latest-gke + commands: + - func: install dependencies + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + env: + DRIVERS_TOOLS: ${DRIVERS_TOOLS} + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + ENVIRONMENT: k8s + K8S_VARIANT: gke + SCRIPT: run-oidc-prose-tests.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + args: + - .evergreen/run-oidc-tests-k8s.sh + - name: oidc-auth-test-k8s-latest-aks + commands: + - func: install dependencies + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + env: + DRIVERS_TOOLS: ${DRIVERS_TOOLS} + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + ENVIRONMENT: k8s + K8S_VARIANT: aks + SCRIPT: run-oidc-prose-tests.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + args: + - .evergreen/run-oidc-tests-k8s.sh - name: oidc-auth-test-azure-latest commands: - func: install dependencies @@ -4410,6 +4470,69 @@ task_groups: - ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/teardown.sh tasks: - test-azurekms-task + - name: testk8soidc_task_group_eks + setup_group: + - func: fetch source + - command: ec2.assume_role + params: + role_arn: ${OIDC_AWS_ROLE_ARN} + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-k8s-latest-eks + - name: testk8soidc_task_group_gke + setup_group: + - func: fetch source + - command: ec2.assume_role + params: + role_arn: ${OIDC_AWS_ROLE_ARN} + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-k8s-latest-gke + - name: testk8soidc_task_group_aks + setup_group: + - func: fetch source + - command: ec2.assume_role + params: + role_arn: ${OIDC_AWS_ROLE_ARN} + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-k8s-latest-aks - name: testtestoidc_task_group setup_group: - func: fetch source @@ -5093,6 +5216,9 @@ buildvariants: - testtestoidc_task_group - testazureoidc_task_group - testgcpoidc_task_group + - testk8soidc_task_group_eks + - testk8soidc_task_group_gke + - testk8soidc_task_group_aks - name: rhel8-test-atlas display_name: Atlas Cluster Tests run_on: rhel80-large diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 69a563c99b4..e51f519b7b6 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -721,7 +721,10 @@ BUILD_VARIANTS.push({ tasks: [ 'testtestoidc_task_group', 'testazureoidc_task_group', - 'testgcpoidc_task_group' + 'testgcpoidc_task_group', + 'testk8soidc_task_group_eks', + 'testk8soidc_task_group_gke', + 'testk8soidc_task_group_aks' ] }); diff --git a/.evergreen/run-oidc-prose-tests.sh b/.evergreen/run-oidc-prose-tests.sh index 1f19612979f..0be8b1e0dce 100755 --- a/.evergreen/run-oidc-prose-tests.sh +++ b/.evergreen/run-oidc-prose-tests.sh @@ -19,10 +19,16 @@ if [ "$ENVIRONMENT" = "azure" ]; then npm run check:oidc-azure elif [ "$ENVIRONMENT" = "gcp" ]; then npm run check:oidc-gcp -else +elif [ "$ENVIRONMENT" = "test" ]; then if [ -z "${OIDC_TOKEN_FILE}" ]; then echo "Must specify OIDC_TOKEN_FILE" exit 1 fi npm run check:oidc-test +else + if [ -z "${K8S_VARIANT}" ]; then + echo "Must specify K8S_VARIANT" + exit 1 + fi + npm run check:oidc-k8s fi diff --git a/.evergreen/run-oidc-tests-k8s.sh b/.evergreen/run-oidc-tests-k8s.sh new file mode 100644 index 00000000000..261c6df5f52 --- /dev/null +++ b/.evergreen/run-oidc-tests-k8s.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 + +export K8S_DRIVERS_TAR_FILE=/tmp/node-mongodb-native.tgz +cd .. +tar -czf $K8S_DRIVERS_TAR_FILE src drivers-tools +cd - +bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/setup-pod.sh +bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/run-self-test.sh +export K8S_TEST_CMD="cd src && DRIVERS_TOOLS=${DRIVERS_TOOLS} ENVIRONMENT=k8s ./.evergreen/${SCRIPT}" +source $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/secrets-export.sh +bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/run-driver-test.sh +bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/teardown-pod.sh \ No newline at end of file diff --git a/package.json b/package.json index 5e76162eb81..23b97cf82b6 100644 --- a/package.json +++ b/package.json @@ -151,6 +151,7 @@ "check:oidc-test": "mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_oidc.prose.test.ts", "check:oidc-azure": "mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_oidc_azure.prose.05.test.ts", "check:oidc-gcp": "mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_oidc_gcp.prose.06.test.ts", + "check:oidc-k8s": "mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_oidc_k8s.prose.07.test.ts", "check:ocsp": "mocha --config test/manual/mocharc.json test/manual/ocsp_support.test.js", "check:kerberos": "nyc mocha --config test/manual/mocharc.json test/manual/kerberos.test.ts", "check:tls": "mocha --config test/manual/mocharc.json test/manual/tls_support.test.ts", diff --git a/src/cmap/auth/mongo_credentials.ts b/src/cmap/auth/mongo_credentials.ts index 00cbfc6b004..97c457945df 100644 --- a/src/cmap/auth/mongo_credentials.ts +++ b/src/cmap/auth/mongo_credentials.ts @@ -31,7 +31,8 @@ function getDefaultAuthMechanism(hello: Document | null): AuthMechanism { const ALLOWED_ENVIRONMENT_NAMES: AuthMechanismProperties['ENVIRONMENT'][] = [ 'test', 'azure', - 'gcp' + 'gcp', + 'k8s' ]; const ALLOWED_HOSTS_ERROR = 'Auth mechanism property ALLOWED_HOSTS must be an array of strings.'; @@ -62,7 +63,7 @@ export interface AuthMechanismProperties extends Document { /** A user provided OIDC human interacted callback function. */ OIDC_HUMAN_CALLBACK?: OIDCCallbackFunction; /** The OIDC environment. Note that 'test' is for internal use only. */ - ENVIRONMENT?: 'test' | 'azure' | 'gcp'; + ENVIRONMENT?: 'test' | 'azure' | 'gcp' | 'k8s'; /** Allowed hosts that OIDC auth can connect to. */ ALLOWED_HOSTS?: string[]; /** The resource token for OIDC auth in Azure and GCP. */ diff --git a/src/cmap/auth/mongodb_oidc.ts b/src/cmap/auth/mongodb_oidc.ts index d4b2a843aea..1cd249aac21 100644 --- a/src/cmap/auth/mongodb_oidc.ts +++ b/src/cmap/auth/mongodb_oidc.ts @@ -6,6 +6,7 @@ import { type AuthContext, AuthProvider } from './auth_provider'; import type { MongoCredentials } from './mongo_credentials'; import { AzureMachineWorkflow } from './mongodb_oidc/azure_machine_workflow'; import { GCPMachineWorkflow } from './mongodb_oidc/gcp_machine_workflow'; +import { K8SMachineWorkflow } from './mongodb_oidc/k8s_machine_workflow'; import { TokenCache } from './mongodb_oidc/token_cache'; import { TokenMachineWorkflow } from './mongodb_oidc/token_machine_workflow'; @@ -88,7 +89,7 @@ export type OIDCCallbackFunction = (params: OIDCCallbackParams) => Promise Workflow> = new Map(); OIDC_WORKFLOWS.set('test', () => new TokenMachineWorkflow(new TokenCache())); OIDC_WORKFLOWS.set('azure', () => new AzureMachineWorkflow(new TokenCache())); OIDC_WORKFLOWS.set('gcp', () => new GCPMachineWorkflow(new TokenCache())); +OIDC_WORKFLOWS.set('k8s', () => new K8SMachineWorkflow(new TokenCache())); /** * OIDC auth provider. diff --git a/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts b/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts new file mode 100644 index 00000000000..22dc9cb9f62 --- /dev/null +++ b/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts @@ -0,0 +1,38 @@ +import { readFile } from 'fs/promises'; + +import { type AccessToken, MachineWorkflow } from './machine_workflow'; +import { type TokenCache } from './token_cache'; + +/** The fallback file name */ +const FALLBACK_FILENAME = '/var/run/secrets/kubernetes.io/serviceaccount/token'; + +/** The azure environment variable for the file name. */ +const AZURE_FILENAME = 'AZURE_FEDERATED_TOKEN_FILE'; + +/** The AWS environment variable for the file name. */ +const AWS_FILENAME = 'AWS_WEB_IDENTITY_TOKEN_FILE'; + +export class K8SMachineWorkflow extends MachineWorkflow { + /** + * Instantiate the machine workflow. + */ + constructor(cache: TokenCache) { + super(cache); + } + + /** + * Get the token from the environment. + */ + async getToken(): Promise { + let filename: string; + if (process.env[AZURE_FILENAME]) { + filename = process.env[AZURE_FILENAME]; + } else if (process.env[AWS_FILENAME]) { + filename = process.env[AWS_FILENAME]; + } else { + filename = FALLBACK_FILENAME; + } + const token = await readFile(filename, 'utf8'); + return { access_token: token }; + } +} diff --git a/test/integration/auth/mongodb_oidc_k8s.prose.07.test.ts b/test/integration/auth/mongodb_oidc_k8s.prose.07.test.ts new file mode 100644 index 00000000000..de1c5ca49e6 --- /dev/null +++ b/test/integration/auth/mongodb_oidc_k8s.prose.07.test.ts @@ -0,0 +1,38 @@ +import { expect } from 'chai'; + +import { type Collection, MongoClient } from '../../mongodb'; + +const DEFAULT_URI = 'mongodb://127.0.0.1:27017'; + +describe('OIDC Auth Spec K8s Tests', function () { + // Note there is no spec or tests for K8s, and it's optional to run the entire + // machine prose tests on the additional environments so we do 1 sanity check + // here. This same test will run in CI on AKS, EKS, and GKE. + describe('7. K8s Tests', function () { + let client: MongoClient; + let collection: Collection; + + beforeEach(function () { + if (!this.configuration.isOIDC(process.env.MONGODB_URI_SINGLE, 'k8s')) { + this.skipReason = 'K8s OIDC prose tests require a K8s OIDC environment.'; + this.skip(); + } + }); + + afterEach(async function () { + await client?.close(); + }); + + describe('7.1 K8s With Environment Set', function () { + beforeEach(function () { + client = new MongoClient(process.env.MONGODB_URI_SINGLE ?? DEFAULT_URI); + collection = client.db('test').collection('test'); + }); + + it('successfully authenticates', async function () { + const result = await collection.findOne(); + expect(result).to.not.be.null; + }); + }); + }); +}); diff --git a/test/spec/auth/legacy/connection-string.json b/test/spec/auth/legacy/connection-string.json index 67aafbff6ee..3a099c81379 100644 --- a/test/spec/auth/legacy/connection-string.json +++ b/test/spec/auth/legacy/connection-string.json @@ -626,6 +626,26 @@ "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp", "valid": false, "credential": null + }, + { + "description": "should recognise the mechanism with k8s provider (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "k8s" + } + } + }, + { + "description": "should throw an error for a username and password with k8s provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s", + "valid": false, + "credential": null } ] } diff --git a/test/spec/auth/legacy/connection-string.yml b/test/spec/auth/legacy/connection-string.yml index ded258f29dd..6b82ef42258 100644 --- a/test/spec/auth/legacy/connection-string.yml +++ b/test/spec/auth/legacy/connection-string.yml @@ -454,3 +454,18 @@ tests: uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp valid: false credential: null +- description: should recognise the mechanism with k8s provider (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s + valid: true + credential: + username: null + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: k8s +- description: should throw an error for a username and password with k8s provider + (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s + valid: false + credential: null \ No newline at end of file