Skip to content

Commit b5338a5

Browse files
authored
CDRIVER-4611 Support ENVIRONMENT:gcp for MONGODB-OIDC (#2177)
* add path and optional audience args to `gcp_request_init` * add `gcp_identity_token_from_gcp_server` * Use a separate helper from `gcp_access_token_from_gcp_server`: the response format differs, a timer is required, an audience is required, and the path differs. * implement `mongoc_oidc_env_fn_gcp`
1 parent 424067e commit b5338a5

File tree

11 files changed

+391
-24
lines changed

11 files changed

+391
-24
lines changed

.evergreen/config_generator/components/oidc.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,29 @@ def task_groups():
5858
),
5959
],
6060
),
61+
EvgTaskGroup(
62+
name='test-oidc-gcp-task-group',
63+
tasks=['oidc-gcp-auth-test-task'],
64+
setup_group_can_fail_task=True,
65+
teardown_group_can_fail_task=True,
66+
teardown_group_timeout_secs=180, # 3 minutes
67+
setup_group=[
68+
FetchDET.call(),
69+
ec2_assume_role(role_arn='${aws_test_secrets_role}'),
70+
bash_exec(
71+
command_type=EvgCommandType.SETUP,
72+
include_expansions_in_env=['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN'],
73+
env={'GCPOIDC_VMNAME_PREFIX': 'CDRIVER'},
74+
script='./drivers-evergreen-tools/.evergreen/auth_oidc/gcp/setup.sh',
75+
),
76+
],
77+
teardown_group=[
78+
bash_exec(
79+
command_type=EvgCommandType.SETUP,
80+
script='./drivers-evergreen-tools/.evergreen/auth_oidc/gcp/teardown.sh',
81+
),
82+
],
83+
),
6184
]
6285

6386

@@ -105,6 +128,29 @@ def tasks():
105128
),
106129
],
107130
),
131+
EvgTask(
132+
name='oidc-gcp-auth-test-task',
133+
run_on=['debian11-small'], # TODO: switch to 'debian11-latest' after DEVPROD-23011 fixed.
134+
commands=[
135+
FetchSource.call(),
136+
bash_exec(
137+
working_dir='mongoc',
138+
add_expansions_to_env=True,
139+
command_type=EvgCommandType.TEST,
140+
script='.evergreen/scripts/oidc-gcp-compile.sh',
141+
),
142+
expansions_update(file='mongoc/oidc-remote-test-expansion.yml'),
143+
bash_exec(
144+
add_expansions_to_env=True,
145+
command_type=EvgCommandType.TEST,
146+
env={
147+
'GCPOIDC_DRIVERS_TAR_FILE': '${OIDC_TEST_TARBALL}',
148+
'GCPOIDC_TEST_CMD': 'source ./secrets-export.sh && ./.evergreen/scripts/oidc-gcp-test.sh',
149+
},
150+
script='./drivers-evergreen-tools/.evergreen/auth_oidc/gcp/run-driver-test.sh',
151+
),
152+
],
153+
),
108154
]
109155

110156

@@ -113,6 +159,10 @@ def variants():
113159
BuildVariant(
114160
name='oidc',
115161
display_name='OIDC',
116-
tasks=[EvgTaskRef(name='test-oidc-task-group'), EvgTaskRef(name='test-oidc-azure-task-group')],
162+
tasks=[
163+
EvgTaskRef(name='test-oidc-task-group'),
164+
EvgTaskRef(name='test-oidc-azure-task-group'),
165+
EvgTaskRef(name='test-oidc-gcp-task-group'),
166+
],
117167
),
118168
]

.evergreen/generated_configs/task_groups.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,37 @@ task_groups:
3030
- -c
3131
- ./drivers-evergreen-tools/.evergreen/auth_oidc/azure/delete-vm.sh
3232
teardown_group_timeout_secs: 180
33+
- name: test-oidc-gcp-task-group
34+
setup_group:
35+
- func: fetch-det
36+
- command: ec2.assume_role
37+
params:
38+
role_arn: ${aws_test_secrets_role}
39+
- command: subprocess.exec
40+
type: setup
41+
params:
42+
binary: bash
43+
env:
44+
GCPOIDC_VMNAME_PREFIX: CDRIVER
45+
include_expansions_in_env:
46+
- AWS_ACCESS_KEY_ID
47+
- AWS_SECRET_ACCESS_KEY
48+
- AWS_SESSION_TOKEN
49+
args:
50+
- -c
51+
- ./drivers-evergreen-tools/.evergreen/auth_oidc/gcp/setup.sh
52+
setup_group_can_fail_task: true
53+
tasks:
54+
- oidc-gcp-auth-test-task
55+
teardown_group:
56+
- command: subprocess.exec
57+
type: setup
58+
params:
59+
binary: bash
60+
args:
61+
- -c
62+
- ./drivers-evergreen-tools/.evergreen/auth_oidc/gcp/teardown.sh
63+
teardown_group_timeout_secs: 180
3364
- name: test-oidc-task-group
3465
setup_group:
3566
- func: fetch-det

.evergreen/generated_configs/tasks.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4249,6 +4249,34 @@ tasks:
42494249
args:
42504250
- -c
42514251
- ./drivers-evergreen-tools/.evergreen/auth_oidc/azure/run-driver-test.sh
4252+
- name: oidc-gcp-auth-test-task
4253+
run_on:
4254+
- debian11-small
4255+
commands:
4256+
- func: fetch-source
4257+
- command: subprocess.exec
4258+
type: test
4259+
params:
4260+
binary: bash
4261+
working_dir: mongoc
4262+
add_expansions_to_env: true
4263+
args:
4264+
- -c
4265+
- .evergreen/scripts/oidc-gcp-compile.sh
4266+
- command: expansions.update
4267+
params:
4268+
file: mongoc/oidc-remote-test-expansion.yml
4269+
- command: subprocess.exec
4270+
type: test
4271+
params:
4272+
binary: bash
4273+
add_expansions_to_env: true
4274+
env:
4275+
GCPOIDC_DRIVERS_TAR_FILE: ${OIDC_TEST_TARBALL}
4276+
GCPOIDC_TEST_CMD: source ./secrets-export.sh && ./.evergreen/scripts/oidc-gcp-test.sh
4277+
args:
4278+
- -c
4279+
- ./drivers-evergreen-tools/.evergreen/auth_oidc/gcp/run-driver-test.sh
42524280
- name: openssl-compat-1.0.2-shared-ubuntu2404-gcc
42534281
run_on: ubuntu2404-large
42544282
tags: [openssl-compat, openssl-1.0.2, openssl-shared, ubuntu2404, gcc]

.evergreen/generated_configs/variants.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ buildvariants:
258258
tasks:
259259
- name: test-oidc-task-group
260260
- name: test-oidc-azure-task-group
261+
- name: test-oidc-gcp-task-group
261262
- name: openssl-compat-matrix
262263
display_name: OpenSSL Compatibility Matrix
263264
tasks:
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 mongoc-ping ... begin"
25+
# Disable unnecessary dependencies. mongoc-ping 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=ON # To support mongodb+srv URIs
32+
-DENABLE_CLIENT_SIDE_ENCRYPTION=OFF
33+
-DENABLE_EXAMPLES=ON # To build mongoc-ping
34+
)
35+
cmake "${cmake_flags[@]}" -Bcmake-build
36+
cmake --build cmake-build --target mongoc-ping
37+
echo "Compile mongoc-ping ... end"
38+
39+
# Create tarball for remote testing.
40+
echo "Creating mongoc-ping tarball ... begin"
41+
42+
# Copy test binary and test script.
43+
files=(
44+
.evergreen/scripts/oidc-gcp-test.sh
45+
cmake-build/src/libmongoc/libmongoc2*
46+
cmake-build/src/libbson/libbson2*
47+
cmake-build/src/libmongoc/mongoc-ping
48+
)
49+
tar -czf mongoc-ping.tar.gz "${files[@]}"
50+
echo "Creating mongoc-ping tarball ... end"
51+
52+
cat <<EOT >oidc-remote-test-expansion.yml
53+
OIDC_TEST_TARBALL: $(pwd)/mongoc-ping.tar.gz
54+
EOT
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
set -o errexit
3+
set -o pipefail
4+
set -o nounset
5+
6+
# Install required OpenSSL runtime library.
7+
sudo apt install -y libssl-dev
8+
9+
mongoc_ping=./cmake-build/src/libmongoc/mongoc-ping
10+
11+
export LD_LIBRARY_PATH
12+
LD_LIBRARY_PATH="$(pwd)/cmake-build/src/libmongoc:$(pwd)/cmake-build/src/libbson:${LD_LIBRARY_PATH:-}"
13+
14+
echo "Testing good auth ..."
15+
if ! "$mongoc_ping" "$MONGODB_URI?authMechanism=MONGODB-OIDC&authSource=%24external&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:$GCPOIDC_AUDIENCE" &>output.txt; then
16+
echo "mongoc-ping failed to authenticate using OIDC in GCP" 1>&2
17+
cat output.txt 1>&2
18+
exit 1
19+
fi
20+
echo "Testing good auth ... done"
21+
22+
echo "Testing bad TOKEN_RESOURCE ..."
23+
if "$mongoc_ping" "$MONGODB_URI?authMechanism=MONGODB-OIDC&authSource=%24external&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:bad" &>output.txt; then
24+
echo "mongoc-ping unexpectedly succeeded with bad token resource" 1>&2
25+
exit 1
26+
fi
27+
echo "Testing bad TOKEN_RESOURCE ... done"

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ typedef struct mcd_azure_access_token {
4646
/**
4747
* @brief Try to parse an Azure access token from an IMDS metadata JSON response
4848
*
49-
* @param out The token to initialize. Should be uninitialized. Must later be
50-
* destroyed by the caller.
49+
* @param out Overwritten with the obtained token. Must later be destroyed.
50+
* @pre `*out` must be in a non-owning state (e.g. uninitialized or zero-initialized).
5151
* @param json The JSON string body
5252
* @param len The length of 'body'
5353
* @param error An output parameter for errors
@@ -92,7 +92,7 @@ typedef struct mcd_azure_imds_request {
9292
* @brief Initialize a new IMDS HTTP request
9393
*
9494
* @param out The object to initialize
95-
* @param token_resource Percent encoded and passed as the "resource" query parameter.
95+
* @param token_resource Will be percent encoded and passed as the "resource" query parameter.
9696
* @param opt_imds_host (Optional) the IP host of the IMDS server
9797
* @param opt_port (Optional) The port of the IMDS HTTP server (default is 80)
9898
* @param opt_extra_headers (Optional) Set extra HTTP headers for the request
@@ -123,9 +123,9 @@ mcd_azure_imds_request_destroy(mcd_azure_imds_request *req);
123123
* @brief Attempt to obtain a new OAuth2 access token from an Azure IMDS HTTP
124124
* server.
125125
*
126-
* @param out The output parameter for the obtained token. Must later be
127-
* destroyed
128-
* @param token_resource Percent encoded and passed as the "resource" query parameter.
126+
* @param out Overwritten with the obtained token. Must later be destroyed.
127+
* @pre `*out` must be in a non-owning state (e.g. uninitialized or zero-initialized).
128+
* @param token_resource Will be percent encoded and passed as the "resource" query parameter.
129129
* @param opt_imds_host (Optional) Override the IP host of the IMDS server
130130
* @param opt_port (Optional) The port of the IMDS HTTP server (default is 80)
131131
* @param opt_extra_headers (Optional) Set extra HTTP headers for the request

src/libmongoc/src/mongoc/mongoc-oidc-env.c

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <mongoc/mcd-azure.h>
2020
#include <mongoc/mongoc-oidc-callback.h>
21+
#include <mongoc/service-gcp.h>
2122

2223
#include <mlib/duration.h>
2324
#include <mlib/time_point.h>
@@ -101,9 +102,39 @@ mongoc_oidc_env_fn_azure(mongoc_oidc_callback_params_t *params)
101102
static mongoc_oidc_credential_t *
102103
mongoc_oidc_env_fn_gcp(mongoc_oidc_callback_params_t *params)
103104
{
104-
BSON_UNUSED(params);
105-
// TODO (CDRIVER-4489)
106-
return NULL;
105+
BSON_ASSERT_PARAM(params);
106+
107+
bson_error_t error;
108+
gcp_service_account_token token = {0};
109+
mongoc_oidc_credential_t *ret = NULL;
110+
mongoc_oidc_env_callback_t *callback = mongoc_oidc_callback_params_get_user_data(params);
111+
BSON_ASSERT(callback);
112+
113+
mlib_timer timer = {0};
114+
const int64_t *timeout_us = mongoc_oidc_callback_params_get_timeout(params);
115+
if (timeout_us) {
116+
timer = mlib_expires_at((mlib_time_point){.time_since_monotonic_start = mlib_duration(*timeout_us, us)});
117+
if (mlib_timer_is_expired(timer)) {
118+
// No time remaining. Immediately fail.
119+
mongoc_oidc_callback_params_cancel_with_timeout(params);
120+
goto fail;
121+
}
122+
}
123+
124+
if (!gcp_identity_token_from_gcp_server(&token, callback->token_resource, timer, &error)) {
125+
MONGOC_ERROR("Failed to obtain GCP OIDC access token: %s", error.message);
126+
goto fail;
127+
}
128+
129+
ret = mongoc_oidc_credential_new(token.access_token);
130+
if (!ret) {
131+
MONGOC_ERROR("Failed to process GCP OIDC access token");
132+
goto fail;
133+
}
134+
135+
fail:
136+
gcp_access_token_destroy(&token);
137+
return ret;
107138
}
108139

109140
static mongoc_oidc_credential_t *

0 commit comments

Comments
 (0)