Skip to content

Commit e35f024

Browse files
authored
CDRIVER-4548 Support ENVIRONMENT:azure for MONGODB-OIDC. (#2166)
* add `mongoc_percent_encode` * extend `mcd-azure.h` * Support a custom token resource, optional timeout, and optional client_id. * add OIDC prose tests 5.1 and 5.2 * implement `mongoc_oidc_env_fn_azure` * apply OIDC environment callback * And move check that both env+user callbacks are not set from auth to cache (to simplify). * test more bad configuration cases * support unified tests with Azure OIDC * add Azure OIDC Evergreen task
1 parent 006eaa7 commit e35f024

25 files changed

+635
-77
lines changed

.evergreen/config_generator/components/oidc.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ def task_groups():
1717
name='test-oidc-task-group',
1818
tasks=['oidc-auth-test-task'],
1919
setup_group_can_fail_task=True,
20-
setup_group_timeout_secs=60 * 60, # 1 hour
2120
teardown_group_can_fail_task=True,
2221
teardown_group_timeout_secs=180, # 3 minutes
2322
setup_group=[
@@ -35,7 +34,30 @@ def task_groups():
3534
script='./drivers-evergreen-tools/.evergreen/auth_oidc/teardown.sh',
3635
)
3736
],
38-
)
37+
),
38+
EvgTaskGroup(
39+
name='test-oidc-azure-task-group',
40+
tasks=['oidc-azure-auth-test-task'],
41+
setup_group_can_fail_task=True,
42+
teardown_group_can_fail_task=True,
43+
teardown_group_timeout_secs=180, # 3 minutes
44+
setup_group=[
45+
FetchDET.call(),
46+
ec2_assume_role(role_arn='${aws_test_secrets_role}'),
47+
bash_exec(
48+
command_type=EvgCommandType.SETUP,
49+
include_expansions_in_env=['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN'],
50+
env={'AZUREOIDC_VMNAME_PREFIX': 'CDRIVER'},
51+
script='./drivers-evergreen-tools/.evergreen/auth_oidc/azure/create-and-setup-vm.sh',
52+
),
53+
],
54+
teardown_group=[
55+
bash_exec(
56+
command_type=EvgCommandType.SETUP,
57+
script='./drivers-evergreen-tools/.evergreen/auth_oidc/azure/delete-vm.sh',
58+
),
59+
],
60+
),
3961
]
4062

4163

@@ -59,7 +81,30 @@ def tasks():
5981
),
6082
RunTests.call(),
6183
],
62-
)
84+
),
85+
EvgTask(
86+
name='oidc-azure-auth-test-task',
87+
run_on=['debian11-small'], # TODO: switch to 'debian11-latest' after DEVPROD-23011 fixed.
88+
commands=[
89+
FetchSource.call(),
90+
bash_exec(
91+
working_dir='mongoc',
92+
add_expansions_to_env=True,
93+
command_type=EvgCommandType.TEST,
94+
script='.evergreen/scripts/oidc-azure-compile.sh',
95+
),
96+
expansions_update(file='mongoc/oidc-remote-test-expansion.yml'),
97+
bash_exec(
98+
add_expansions_to_env=True,
99+
command_type=EvgCommandType.TEST,
100+
env={
101+
'AZUREOIDC_DRIVERS_TAR_FILE': '${OIDC_TEST_TARBALL}',
102+
'AZUREOIDC_TEST_CMD': 'source ./env.sh && ./.evergreen/scripts/oidc-azure-test.sh',
103+
},
104+
script='./drivers-evergreen-tools/.evergreen/auth_oidc/azure/run-driver-test.sh',
105+
),
106+
],
107+
),
63108
]
64109

65110

@@ -68,7 +113,6 @@ def variants():
68113
BuildVariant(
69114
name='oidc',
70115
display_name='OIDC',
71-
run_on=[find_small_distro('ubuntu2404').name],
72-
tasks=[EvgTaskRef(name='test-oidc-task-group')],
116+
tasks=[EvgTaskRef(name='test-oidc-task-group'), EvgTaskRef(name='test-oidc-azure-task-group')],
73117
),
74118
]

.evergreen/generated_configs/task_groups.yml

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,35 @@
11
task_groups:
2+
- name: test-oidc-azure-task-group
3+
setup_group:
4+
- func: fetch-det
5+
- command: ec2.assume_role
6+
params:
7+
role_arn: ${aws_test_secrets_role}
8+
- command: subprocess.exec
9+
type: setup
10+
params:
11+
binary: bash
12+
env:
13+
AZUREOIDC_VMNAME_PREFIX: CDRIVER
14+
include_expansions_in_env:
15+
- AWS_ACCESS_KEY_ID
16+
- AWS_SECRET_ACCESS_KEY
17+
- AWS_SESSION_TOKEN
18+
args:
19+
- -c
20+
- ./drivers-evergreen-tools/.evergreen/auth_oidc/azure/create-and-setup-vm.sh
21+
setup_group_can_fail_task: true
22+
tasks:
23+
- oidc-azure-auth-test-task
24+
teardown_group:
25+
- command: subprocess.exec
26+
type: setup
27+
params:
28+
binary: bash
29+
args:
30+
- -c
31+
- ./drivers-evergreen-tools/.evergreen/auth_oidc/azure/delete-vm.sh
32+
teardown_group_timeout_secs: 180
233
- name: test-oidc-task-group
334
setup_group:
435
- func: fetch-det
@@ -17,7 +48,6 @@ task_groups:
1748
- -c
1849
- ./drivers-evergreen-tools/.evergreen/auth_oidc/setup.sh
1950
setup_group_can_fail_task: true
20-
setup_group_timeout_secs: 3600
2151
tasks:
2252
- oidc-auth-test-task
2353
teardown_group:

.evergreen/generated_configs/tasks.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4221,6 +4221,34 @@ tasks:
42214221
- { key: MONGODB_VERSION, value: latest }
42224222
- { key: TOPOLOGY, value: replica_set }
42234223
- func: run-tests
4224+
- name: oidc-azure-auth-test-task
4225+
run_on:
4226+
- debian11-small
4227+
commands:
4228+
- func: fetch-source
4229+
- command: subprocess.exec
4230+
type: test
4231+
params:
4232+
binary: bash
4233+
working_dir: mongoc
4234+
add_expansions_to_env: true
4235+
args:
4236+
- -c
4237+
- .evergreen/scripts/oidc-azure-compile.sh
4238+
- command: expansions.update
4239+
params:
4240+
file: mongoc/oidc-remote-test-expansion.yml
4241+
- command: subprocess.exec
4242+
type: test
4243+
params:
4244+
binary: bash
4245+
add_expansions_to_env: true
4246+
env:
4247+
AZUREOIDC_DRIVERS_TAR_FILE: ${OIDC_TEST_TARBALL}
4248+
AZUREOIDC_TEST_CMD: source ./env.sh && ./.evergreen/scripts/oidc-azure-test.sh
4249+
args:
4250+
- -c
4251+
- ./drivers-evergreen-tools/.evergreen/auth_oidc/azure/run-driver-test.sh
42244252
- name: openssl-compat-1.0.2-shared-ubuntu2404-gcc
42254253
run_on: ubuntu2404-large
42264254
tags: [openssl-compat, openssl-1.0.2, openssl-shared, ubuntu2404, gcc]

.evergreen/generated_configs/variants.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,9 @@ buildvariants:
255255
- name: mock-server-test
256256
- name: oidc
257257
display_name: OIDC
258-
run_on:
259-
- ubuntu2404-small
260258
tasks:
261259
- name: test-oidc-task-group
260+
- name: test-oidc-azure-task-group
262261
- name: openssl-compat-matrix
263262
display_name: OpenSSL Compatibility Matrix
264263
tasks:
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env bash
2+
set -o errexit
3+
set -o pipefail
4+
set -o nounset
5+
6+
if [[ "${distro_id:?}" == "debian11-small" ]]; then
7+
# Temporary workaround for lack of uv on `debian11`. TODO: remove after DEVPROD-23011 is resolved.
8+
uv_dir="$(mktemp -d)"
9+
python3 -m virtualenv "${uv_dir:?}"
10+
# shellcheck source=/dev/null
11+
(. "${uv_dir:?}/bin/activate" && python -m pip install uv)
12+
PATH="${uv_dir:?}/bin:${PATH:-}"
13+
command -V uv >/dev/null
14+
fi
15+
16+
. .evergreen/scripts/install-build-tools.sh
17+
install_build_tools
18+
export CMAKE_GENERATOR="Ninja"
19+
20+
# Use ccache if able.
21+
. .evergreen/scripts/find-ccache.sh
22+
find_ccache_and_export_vars "$(pwd)" || true
23+
24+
echo "Compile test-libmongoc ... begin"
25+
# Disable unnecessary dependencies. test-libmongoc is copied to a remote host for testing, which may not have all dependent libraries.
26+
cmake_flags=(
27+
-DENABLE_SASL=OFF
28+
-DENABLE_SNAPPY=OFF
29+
-DENABLE_ZSTD=OFF
30+
-DENABLE_ZLIB=OFF
31+
-DENABLE_SRV=OFF
32+
-DENABLE_CLIENT_SIDE_ENCRYPTION=OFF
33+
-DENABLE_EXAMPLES=OFF
34+
-DENABLE_SRV=OFF
35+
)
36+
cmake "${cmake_flags[@]}" -Bcmake-build
37+
cmake --build cmake-build --target test-libmongoc
38+
echo "Compile test-libmongoc ... end"
39+
40+
# Create tarball for remote testing.
41+
echo "Creating test-libmongoc tarball ... begin"
42+
43+
# Copy test binary and JSON test files. All JSON test files are needed to start test-libmongoc.
44+
files=(
45+
.evergreen/scripts/oidc-azure-test.sh
46+
cmake-build/src/libmongoc/test-libmongoc
47+
src/libmongoc/tests/json
48+
src/libbson/tests/json
49+
)
50+
tar -czf test-libmongoc.tar.gz "${files[@]}"
51+
echo "Creating test-libmongoc tarball ... end"
52+
53+
cat <<EOT >oidc-remote-test-expansion.yml
54+
OIDC_TEST_TARBALL: $(pwd)/test-libmongoc.tar.gz
55+
EOT
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
set -o errexit
3+
set -o pipefail
4+
set -o nounset
5+
6+
export MONGOC_TEST_OIDC="ON"
7+
export MONGOC_TEST_USER="$OIDC_ADMIN_USER"
8+
export MONGOC_TEST_PASSWORD="$OIDC_ADMIN_PWD"
9+
export MONGOC_AZURE_RESOURCE="$AZUREOIDC_RESOURCE"
10+
11+
# Install required OpenSSL runtime library.
12+
sudo apt install -y libssl-dev
13+
14+
./cmake-build/src/libmongoc/test-libmongoc -d -l '/auth/unified/*' -l '/oidc/*'

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ To run test cases with large allocations, set:
321321
* `MONGOC_TEST_LARGE_ALLOCATIONS=on` This may result in sudden test suite termination due to allocation failure. Use with caution.
322322

323323
* `MONGOC_TEST_OIDC=on` to test OIDC using a test environment described [here](https://github.com/mongodb-labs/drivers-evergreen-tools/tree/d7a7337b384392a09fbe7fc80a7244e6f1226c18/.evergreen/auth_oidc).
324+
* `MONGOC_AZURE_RESOURCE=<resource>` to test OIDC using an Azure test environment described [here](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/d7a7337b384392a09fbe7fc80a7244e6f1226c18/.evergreen/auth_oidc/azure/README.md).
324325

325326
All tests should pass before submitting a patch.
326327

src/libmongoc/src/mongoc/mcd-azure.c

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,28 @@
2121

2222
#include <mlib/cmp.h>
2323
#include <mlib/duration.h>
24+
#include <mlib/time_point.h>
2425
#include <mlib/timer.h>
2526

2627
#define AZURE_API_VERSION "2018-02-01"
2728

28-
static const char *const DEFAULT_METADATA_PATH =
29-
"/metadata/identity/oauth2/"
30-
"token?api-version=" AZURE_API_VERSION "&resource=https%3A%2F%2Fvault.azure.net";
29+
static const char *const DEFAULT_METADATA_PATH = "/metadata/identity/oauth2/token?api-version=" AZURE_API_VERSION;
3130

32-
void
31+
bool
3332
mcd_azure_imds_request_init(mcd_azure_imds_request *req,
33+
const char *token_resource,
3434
const char *const opt_imds_host,
3535
int opt_port,
36-
const char *const opt_extra_headers)
36+
const char *const opt_extra_headers,
37+
const char *const opt_client_id)
3738
{
3839
BSON_ASSERT_PARAM(req);
40+
BSON_ASSERT_PARAM(token_resource);
41+
42+
bool ok = false;
43+
char *encoded_token_resource = NULL;
44+
mcommon_string_append_t path = {0};
45+
3946
_mongoc_http_request_init(&req->req);
4047
// The HTTP host of the IMDS server
4148
req->req.host = req->_owned_host = bson_strdup(opt_imds_host ? opt_imds_host : "169.254.169.254");
@@ -52,9 +59,33 @@ mcd_azure_imds_request_init(mcd_azure_imds_request *req,
5259
req->req.extra_headers = req->_owned_headers = bson_strdup_printf("Metadata: true\r\n"
5360
"Accept: application/json\r\n%s",
5461
opt_extra_headers ? opt_extra_headers : "");
55-
// The default path is suitable. In the future, we may want to add query
56-
// parameters to disambiguate a managed identity.
57-
req->req.path = req->_owned_path = bson_strdup(DEFAULT_METADATA_PATH);
62+
// Build the path with query parameters.
63+
encoded_token_resource = mongoc_percent_encode(token_resource);
64+
if (!encoded_token_resource) {
65+
goto fail;
66+
}
67+
68+
mcommon_string_new_as_append(&path);
69+
70+
if (!mcommon_string_append(&path, DEFAULT_METADATA_PATH) ||
71+
!mcommon_string_append_printf(&path, "&resource=%s", encoded_token_resource)) {
72+
goto fail;
73+
}
74+
75+
if (opt_client_id) {
76+
if (!mcommon_string_append_printf(&path, "&client_id=%s", opt_client_id)) {
77+
goto fail;
78+
}
79+
}
80+
81+
req->req.path = req->_owned_path = mcommon_string_from_append_destroy_with_steal(&path);
82+
path = (mcommon_string_append_t){0};
83+
84+
ok = true;
85+
fail:
86+
bson_free(encoded_token_resource);
87+
mcommon_string_from_append_destroy(&path);
88+
return ok;
5889
}
5990

6091
void
@@ -158,11 +189,15 @@ mcd_azure_access_token_destroy(mcd_azure_access_token *c)
158189

159190
bool
160191
mcd_azure_access_token_from_imds(mcd_azure_access_token *const out,
192+
const char *token_resource,
161193
const char *const opt_imds_host,
162194
int opt_port,
163195
const char *opt_extra_headers,
196+
mlib_timer opt_timer,
197+
const char *opt_client_id,
164198
bson_error_t *error)
165199
{
200+
BSON_ASSERT_PARAM(token_resource);
166201
BSON_ASSERT_PARAM(out);
167202

168203
bool okay = false;
@@ -174,9 +209,16 @@ mcd_azure_access_token_from_imds(mcd_azure_access_token *const out,
174209
_mongoc_http_response_init(&resp);
175210

176211
mcd_azure_imds_request req = MCD_AZURE_IMDS_REQUEST_INIT;
177-
mcd_azure_imds_request_init(&req, opt_imds_host, opt_port, opt_extra_headers);
212+
if (!mcd_azure_imds_request_init(&req, token_resource, opt_imds_host, opt_port, opt_extra_headers, opt_client_id)) {
213+
_mongoc_set_error(error, MONGOC_ERROR_AZURE, MONGOC_ERROR_KMS_SERVER_HTTP, "Failed to initialize request");
214+
goto fail;
215+
}
216+
217+
mlib_timer timer = mlib_time_cmp(opt_timer.expires_at, ==, (mlib_time_point){0})
218+
? opt_timer
219+
: mlib_expires_after(mlib_duration(3, s)); // Default 3 second timeout.
178220

179-
if (!_mongoc_http_send(&req.req, mlib_expires_after(mlib_duration(3, s)), false, NULL, &resp, error)) {
221+
if (!_mongoc_http_send(&req.req, timer, false, NULL, &resp, error)) {
180222
goto fail;
181223
}
182224

0 commit comments

Comments
 (0)