Skip to content

Commit e498b7e

Browse files
committed
RUBY-3303 Add OIDC machine workflow auth
1 parent 3c5dc93 commit e498b7e

36 files changed

+1858
-378
lines changed

.evergreen/config.yml

+156-1
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,48 @@ functions:
454454
455455
CRYPT_SHARED_LIB_PATH="${CRYPT_SHARED_LIB_PATH}" SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh
456456
457+
"run oidc vm tests":
458+
- command: subprocess.exec
459+
type: test
460+
params:
461+
working_dir: src
462+
binary: bash
463+
env:
464+
DRIVERS_TOOLS: ${DRIVERS_TOOLS}
465+
PROJECT_DIRECTORY: ${PROJECT_DIRECTORY}
466+
RVM_RUBY: ${RVM_RUBY}
467+
TEST_SCRIPT: ${TEST_SCRIPT}
468+
args:
469+
- .evergreen/${RUN_SCRIPT}
470+
471+
"run oidc prose tests":
472+
- command: subprocess.exec
473+
type: test
474+
params:
475+
working_dir: src
476+
binary: bash
477+
env:
478+
DRIVERS_TOOLS: ${DRIVERS_TOOLS}
479+
PROJECT_DIRECTORY: ${PROJECT_DIRECTORY}
480+
ENVIRONMENT: ${ENVIRONMENT}
481+
RVM_RUBY: ${RVM_RUBY}
482+
args:
483+
- .evergreen/run-tests-oidc-prose.sh
484+
485+
"run oidc unified tests":
486+
- command: subprocess.exec
487+
type: test
488+
params:
489+
working_dir: src
490+
binary: bash
491+
env:
492+
DRIVERS_TOOLS: ${DRIVERS_TOOLS}
493+
PROJECT_DIRECTORY: ${PROJECT_DIRECTORY}
494+
ENVIRONMENT: ${ENVIRONMENT}
495+
RVM_RUBY: ${RVM_RUBY}
496+
args:
497+
- .evergreen/run-tests-oidc-unified.sh
498+
457499
pre:
458500
- func: "fetch source"
459501
- func: "create expansions"
@@ -751,6 +793,77 @@ task_groups:
751793
tasks:
752794
- testazurekms-task
753795

796+
- name: test_oidc_task_group
797+
setup_group:
798+
- func: fetch source
799+
- func: create expansions
800+
- command: ec2.assume_role
801+
params:
802+
role_arn: ${aws_test_secrets_role}
803+
- command: subprocess.exec
804+
params:
805+
binary: bash
806+
include_expansions_in_env:
807+
- AWS_ACCESS_KEY_ID
808+
- AWS_SECRET_ACCESS_KEY
809+
- AWS_SESSION_TOKEN
810+
env:
811+
MONGODB_VERSION: '8.0'
812+
args:
813+
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/setup.sh
814+
setup_group_can_fail_task: true
815+
setup_group_timeout_secs: 1800
816+
tasks:
817+
- oidc-auth-test-latest
818+
819+
- name: test_oidc_azure_task_group
820+
setup_group:
821+
- func: fetch source
822+
- func: create expansions
823+
- command: shell.exec
824+
params:
825+
shell: bash
826+
script: |-
827+
set -o errexit
828+
${PREPARE_SHELL}
829+
export AZUREOIDC_VMNAME_PREFIX="RUBY_DRIVER"
830+
$DRIVERS_TOOLS/.evergreen/auth_oidc/azure/setup.sh
831+
teardown_task:
832+
- command: shell.exec
833+
params:
834+
shell: bash
835+
script: |-
836+
${PREPARE_SHELL}
837+
$DRIVERS_TOOLS/.evergreen/auth_oidc/azure/teardown.sh
838+
setup_group_can_fail_task: true
839+
setup_group_timeout_secs: 1800
840+
tasks:
841+
- oidc-auth-test-azure-latest
842+
843+
- name: test_oidc_gcp_task_group
844+
setup_group:
845+
- func: fetch source
846+
- func: create expansions
847+
- command: shell.exec
848+
params:
849+
shell: bash
850+
script: |-
851+
set -o errexit
852+
${PREPARE_SHELL}
853+
export GCPOIDC_VMNAME_PREFIX="RUBY_DRIVER"
854+
$DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/setup.sh
855+
teardown_task:
856+
- command: shell.exec
857+
params:
858+
shell: bash
859+
script: |-
860+
${PREPARE_SHELL}
861+
$DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/teardown.sh
862+
setup_group_can_fail_task: true
863+
setup_group_timeout_secs: 1800
864+
tasks:
865+
- oidc-auth-test-gcp-latest
866+
754867
tasks:
755868
- name: "test-atlas"
756869
commands:
@@ -895,8 +1008,37 @@ tasks:
8951008
LAMBDA_STACK_NAME: "dbx-ruby-lambda"
8961009
RVM_RUBY: ruby-3.2
8971010
MONGODB_URI: ${MONGODB_URI}
898-
axes:
8991011

1012+
- name: oidc-auth-test-latest
1013+
commands:
1014+
- func: "run oidc prose tests"
1015+
vars:
1016+
ENVIRONMENT: test
1017+
- func: "run oidc unified tests"
1018+
vars:
1019+
ENVIRONMENT: test
1020+
1021+
- name: oidc-auth-test-azure-latest
1022+
commands:
1023+
- func: "run oidc vm tests"
1024+
vars:
1025+
TEST_SCRIPT: run-tests-oidc-prose.sh
1026+
RUN_SCRIPT: run-tests-oidc-azure.sh
1027+
- func: "run oidc vm tests"
1028+
vars:
1029+
TEST_SCRIPT: run-tests-oidc-unified.sh
1030+
RUN_SCRIPT: run-tests-oidc-azure.sh
1031+
1032+
- name: oidc-auth-test-gcp-latest
1033+
commands:
1034+
- func: "run oidc prose tests"
1035+
vars:
1036+
ENVIRONMENT: gcp
1037+
- func: "run oidc unified tests"
1038+
vars:
1039+
ENVIRONMENT: gcp
1040+
1041+
axes:
9001042
- id: preload
9011043
display_name: Preload server
9021044
values:
@@ -1898,3 +2040,16 @@ buildvariants:
18982040
display_name: "AWS Lambda"
18992041
tasks:
19002042
- name: test_aws_lambda_task_group
2043+
2044+
- matrix_name: test-oidc-variant
2045+
matrix_spec:
2046+
ruby: "ruby-3.2"
2047+
fle: helper
2048+
topology: standalone
2049+
os: ubuntu2004
2050+
mongodb-version: latest
2051+
display_name: "OIDC auth tests: latest ruby-3.2"
2052+
tasks:
2053+
- test_oidc_task_group
2054+
- test_oidc_azure_task_group
2055+
- test_oidc_gcp_task_group

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

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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/mongo-ruby-driver.tgz
6+
tar czf $AZUREOIDC_DRIVERS_TAR_FILE .
7+
export AZUREOIDC_TEST_CMD="source ./env.sh && ENVIRONMENT=azure ./.evergreen/${TEST_SCRIPT}"
8+
export PROJECT_DIRECTORY=$PROJECT_DIRECTORY
9+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh

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

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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 GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-ruby-driver.tgz
6+
tar czf $GCPOIDC_DRIVERS_TAR_FILE .
7+
export GCPOIDC_TEST_CMD="source ./secrets-export.sh drivers/gcpoidc && ENVIRONMENT=gcp ./.evergreen/${TEST_SCRIPT}"
8+
export PROJECT_DIRECTORY=$PROJECT_DIRECTORY
9+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh

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

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
set -ex
4+
5+
ENVIRONMENT=${ENVIRONMENT:-"test"}
6+
7+
. `dirname "$0"`/../spec/shared/shlib/distro.sh
8+
. `dirname "$0"`/../spec/shared/shlib/set_env.sh
9+
. `dirname "$0"`/functions.sh
10+
11+
set_env_vars
12+
set_env_python
13+
set_env_ruby
14+
15+
bundle_install
16+
bundle exec rspec -fd spec/integration/oidc/${ENVIRONMENT}_machine_auth_flow_prose_spec.rb
17+
18+
test_status=$?
19+
20+
kill_jruby
21+
22+
exit ${test_status}

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

Whitespace-only changes.

.mod/drivers-evergreen-tools

lib/mongo/auth.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
require 'mongo/auth/cr'
2828
require 'mongo/auth/gssapi'
2929
require 'mongo/auth/ldap'
30+
require 'mongo/auth/oidc'
3031
require 'mongo/auth/scram'
3132
require 'mongo/auth/scram256'
3233
require 'mongo/auth/x509'
@@ -70,6 +71,7 @@ module Auth
7071
aws: Aws,
7172
gssapi: Gssapi,
7273
mongodb_cr: CR,
74+
mongodb_oidc: Oidc,
7375
mongodb_x509: X509,
7476
plain: LDAP,
7577
scram: Scram,
@@ -89,7 +91,7 @@ module Auth
8991
# value of speculativeAuthenticate field of hello response of
9092
# the handshake on the specified connection.
9193
#
92-
# @return [ Auth::Aws | Auth::CR | Auth::Gssapi | Auth::LDAP |
94+
# @return [ Auth::Aws | Auth::CR | Auth::Gssapi | Auth::LDAP | Auth::Oidc
9395
# Auth::Scram | Auth::Scram256 | Auth::X509 ] The authenticator.
9496
#
9597
# @since 2.0.0

lib/mongo/auth/oidc.rb

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# frozen_string_literal: true
2+
# rubocop:todo all
3+
4+
# Copyright (C) 2014-2024 MongoDB, Inc.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
module Mongo
19+
module Auth
20+
21+
# Defines behavior for OIDC authentication.
22+
#
23+
# @api private
24+
class Oidc < Base
25+
attr_reader :speculative_auth_result
26+
27+
# The authentication mechanism string.
28+
#
29+
# @since 2.20.0
30+
MECHANISM = 'MONGODB-OIDC'.freeze
31+
32+
# Initializes the OIDC authenticator.
33+
#
34+
# @param [ Auth::User ] user The user to authenticate.
35+
# @param [ Mongo::Connection ] connection The connection to authenticate over.
36+
#
37+
# @option opts [ BSON::Document | nil ] speculative_auth_result The
38+
# value of speculativeAuthenticate field of hello response of
39+
# the handshake on the specified connection.
40+
def initialize(user, connection, **opts)
41+
super
42+
@speculative_auth_result = opts[:speculative_auth_result]
43+
@machine_workflow = MachineWorkflow::new(auth_mech_properties: user.auth_mech_properties)
44+
end
45+
46+
# Log the user in on the current connection.
47+
#
48+
# @return [ BSON::Document ] The document of the authentication response.
49+
def login
50+
execute_workflow(connection: connection, conversation: conversation)
51+
end
52+
53+
private
54+
55+
def execute_workflow(connection:, conversation:)
56+
# If there is a cached access token, try to authenticate with it. If
57+
# authentication fails with an Authentication error (18),
58+
# invalidate the access token, fetch a new access token, and try
59+
# to authenticate again.
60+
# If the server fails for any other reason, do not clear the cache.
61+
if cache.access_token?
62+
token = cache.access_token
63+
msg = conversation.start(connection: connection, token: token)
64+
begin
65+
dispatch_msg(connection, conversation, msg)
66+
rescue AuthError => error
67+
cache.invalidate(token: token)
68+
execute_workflow(connection: connection, conversation: conversation)
69+
end
70+
end
71+
# This is the normal flow when no token is in the cache. Execute the
72+
# machine callback to get the token, put it in the caches, and then
73+
# send the saslStart to the server.
74+
token = machine_workflow.execute
75+
cache.access_token = token
76+
connection.access_token = token
77+
msg = conversation.start(connection: connection, token: token)
78+
dispatch_msg(connection, conversation, msg)
79+
end
80+
end
81+
end
82+
end
83+
84+
require 'mongo/auth/oidc/conversation'
85+
require 'mongo/auth/oidc/machine_workflow'
86+
require 'mongo/auth/oidc/token_cache'

0 commit comments

Comments
 (0)