Skip to content

Commit 6e4126a

Browse files
authored
RUST-1627: Add automatic token acquisition for GCP (#1097)
1 parent 233d695 commit 6e4126a

File tree

6 files changed

+194
-27
lines changed

6 files changed

+194
-27
lines changed

.evergreen/config.yml

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ buildvariants:
307307
tasks:
308308
- testoidc_task_group
309309
- testazureoidc_task_group
310+
- testgcpoidc_task_group
310311

311312
- name: oidc-macos
312313
display_name: "OIDC Macos"
@@ -331,6 +332,7 @@ buildvariants:
331332
tasks:
332333
- testoidc_task_group
333334
- testazureoidc_task_group
335+
- testgcpoidc_task_group
334336

335337
- name: in-use-encryption
336338
display_name: "In-Use Encryption"
@@ -653,14 +655,13 @@ task_groups:
653655
- func: fix absolute paths
654656
- func: init test-results
655657
- func: make files executable
656-
- command: shell.exec
658+
- command: subprocess.exec
657659
params:
658-
shell: bash
660+
binary: bash
659661
env:
660662
AZUREOIDC_VMNAME_PREFIX: "RUST_DRIVER"
661-
script: |
662-
${PREPARE_SHELL}
663-
${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/create-and-setup-vm.sh
663+
args:
664+
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/create-and-setup-vm.sh
664665
teardown_task:
665666
- command: subprocess.exec
666667
params:
@@ -672,6 +673,32 @@ task_groups:
672673
tasks:
673674
- oidc-auth-test-azure-latest
674675

676+
- name: testgcpoidc_task_group
677+
setup_group:
678+
- func: fetch source
679+
- func: create expansions
680+
- func: prepare resources
681+
- func: fix absolute paths
682+
- func: init test-results
683+
- func: make files executable
684+
- command: subprocess.exec
685+
params:
686+
binary: bash
687+
env:
688+
GCPOIDC_VMNAME_PREFIX: "RUST_DRIVER"
689+
args:
690+
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh
691+
teardown_task:
692+
- command: subprocess.exec
693+
params:
694+
binary: bash
695+
args:
696+
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/teardown.sh
697+
setup_group_can_fail_task: true
698+
setup_group_timeout_secs: 1800
699+
tasks:
700+
- oidc-auth-test-gcp-latest
701+
675702
#########
676703
# Tasks #
677704
#########
@@ -1081,15 +1108,53 @@ tasks:
10811108
script: |-
10821109
set -o errexit
10831110
${PREPARE_SHELL}
1084-
git add .
1085-
git commit -m "add files"
1086-
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tgz
1087-
git archive -o $AZUREOIDC_DRIVERS_TAR_FILE HEAD
1088-
export AZUREOIDC_TEST_CMD="PROJECT_DIRECTORY='.' ./.evergreen/install-dependencies.sh rust\
1089-
&& PROJECT_DIRECTORY='.' .evergreen/install-dependencies.sh junit-dependencies\
1090-
&& PROJECT_DIRECTORY='.' OIDC_ENV=azure OIDC=oidc ./.evergreen/run-mongodb-oidc-test.sh"
1111+
./.evergreen/install-dependencies.sh rust
1112+
source .cargo/env
1113+
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tar
1114+
rustup default stable
1115+
export RUSTFLAGS="-C target-feature=+crt-static"
1116+
cargo test --features azure-oidc --target x86_64-unknown-linux-gnu get_exe_name -- --ignored
1117+
export TEST_FILE=$(cat exe_name.txt)
1118+
rm "$AZUREOIDC_DRIVERS_TAR_FILE" || true
1119+
tar -cf $AZUREOIDC_DRIVERS_TAR_FILE $TEST_FILE
1120+
tar -uf $AZUREOIDC_DRIVERS_TAR_FILE ./.evergreen
1121+
rm "$AZUREOIDC_DRIVERS_TAR_FILE".gz || true
1122+
gzip $AZUREOIDC_DRIVERS_TAR_FILE
1123+
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tar.gz
1124+
# Define the command to run on the azure VM.
1125+
# Ensure that we source the environment file created for us, set up any other variables we need,
1126+
# and then run our test suite on the vm.
1127+
export AZUREOIDC_TEST_CMD="ls -laR data && PROJECT_DIRECTORY='.' OIDC_ENV=azure OIDC=oidc TEST_FILE=./$TEST_FILE ./.evergreen/run-mongodb-oidc-test.sh"
10911128
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh
10921129
1130+
- name: "oidc-auth-test-gcp-latest"
1131+
commands:
1132+
- command: shell.exec
1133+
params:
1134+
working_dir: src
1135+
shell: bash
1136+
script: |-
1137+
set -o errexit
1138+
${PREPARE_SHELL}
1139+
./.evergreen/install-dependencies.sh rust
1140+
source .cargo/env
1141+
export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tar
1142+
rustup default stable
1143+
export RUSTFLAGS="-C target-feature=+crt-static"
1144+
cargo test --features gcp-oidc --target x86_64-unknown-linux-gnu test::atlas_planned_maintenance_testing::get_exe_name -- --ignored
1145+
export TEST_FILE=$(cat exe_name.txt)
1146+
rm "$GCPOIDC_DRIVERS_TAR_FILE" || true
1147+
tar -cf $GCPOIDC_DRIVERS_TAR_FILE $TEST_FILE
1148+
tar -uf $GCPOIDC_DRIVERS_TAR_FILE ./.evergreen
1149+
rm "$GCPOIDC_DRIVERS_TAR_FILE".gz || true
1150+
gzip $GCPOIDC_DRIVERS_TAR_FILE
1151+
export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tar.gz
1152+
# Define the command to run on the gcp VM.
1153+
# Ensure that we source the environment file created for us, set up any other variables we need,
1154+
# and then run our test suite on the vm.
1155+
export GCPOIDC_TEST_CMD="ls -la && PROJECT_DIRECTORY='.' OIDC_ENV=gcp OIDC=oidc TEST_FILE=./$TEST_FILE ./.evergreen/run-mongodb-oidc-test.sh"
1156+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh
1157+
10931158
#############
10941159
# Functions #
10951160
#############

.evergreen/run-mongodb-oidc-test.sh

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
set +x # Disable debug trace
44
set -o errexit # Exit the script with error if any of the commands fail
55

6-
source .evergreen/env.sh
7-
source .evergreen/cargo-test.sh
8-
96
echo "Running MONGODB-OIDC authentication tests"
107

118
OIDC_ENV=${OIDC_ENV:-"test"}
@@ -15,6 +12,9 @@ export COVERAGE=1
1512
export AUTH="auth"
1613

1714
if [ $OIDC_ENV == "test" ]; then
15+
16+
source .evergreen/env.sh
17+
source .evergreen/cargo-test.sh
1818
# Make sure DRIVERS_TOOLS is set.
1919
if [ -z "$DRIVERS_TOOLS" ]; then
2020
echo "Must specify DRIVERS_TOOLS"
@@ -24,15 +24,20 @@ if [ $OIDC_ENV == "test" ]; then
2424

2525
cargo nextest run test::spec::oidc::basic --no-capture --profile ci
2626
RESULT=$?
27+
cp target/nextest/ci/junit.xml results.xml
2728
elif [ $OIDC_ENV == "azure" ]; then
2829
source ./env.sh
2930

30-
cargo nextest run test::spec::oidc::azure --no-capture --profile ci --features=azure-oidc
31+
$TEST_FILE test::spec::oidc::azure --nocapture
32+
RESULT=$?
33+
elif [ $OIDC_ENV == "gcp" ]; then
34+
source ./secrets-export.sh
35+
36+
$TEST_FILE test::spec::oidc::gcp --nocapture
3137
RESULT=$?
3238
else
3339
echo "Unrecognized OIDC_ENV $OIDC_ENV"
3440
exit 1
3541
fi
3642

37-
cp target/nextest/ci/junit.xml results.xml
3843
exit $RESULT

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ authors = [
55
"Isabel Atkinson <[email protected]>",
66
"Abraham Egnor <[email protected]>",
77
"Kaitlin Mahar <[email protected]>",
8+
"Patrick Meredith <[email protected]>",
89
]
910
description = "The official MongoDB driver for Rust"
1011
edition = "2021"
@@ -45,6 +46,9 @@ azure-kms = ["dep:reqwest"]
4546
# Enable support for azure OIDC authentication.
4647
azure-oidc = ["dep:reqwest"]
4748

49+
# Enable support for gcp OIDC authentication.
50+
gcp-oidc = ["dep:reqwest"]
51+
4852
# Enable support for on-demand GCP KMS credentials.
4953
# This can only be used with the tokio-runtime feature flag.
5054
gcp-kms = ["dep:reqwest"]

src/client/auth/oidc.rs

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77
use tokio::sync::Mutex;
88
use typed_builder::TypedBuilder;
99

10-
#[cfg(feature = "azure-oidc")]
10+
#[cfg(any(feature = "azure-oidc", feature = "gcp-oidc"))]
1111
use crate::client::auth::{
1212
AZURE_ENVIRONMENT_VALUE_STR,
1313
ENVIRONMENT_PROP_STR,
@@ -214,6 +214,46 @@ impl Callback {
214214
cache: Cache::new(),
215215
}
216216
}
217+
218+
/// Create gcp callback.
219+
#[cfg(feature = "gcp-oidc")]
220+
fn gcp_callback(resource: &str) -> CallbackInner {
221+
use futures_util::FutureExt;
222+
let url = format!(
223+
"http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience={}",
224+
resource
225+
);
226+
CallbackInner {
227+
function: Self::new_function(
228+
move |_| {
229+
let url = url.clone();
230+
async move {
231+
let url = url.clone();
232+
let response = crate::runtime::HttpClient::default()
233+
.get(&url)
234+
.headers(&[("Metadata-Flavor", "Google")])
235+
.send_and_get_string()
236+
.await
237+
.map_err(|e| {
238+
Error::authentication_error(
239+
MONGODB_OIDC_STR,
240+
&format!("Failed to get access token from GCP IDMS: {}", e),
241+
)
242+
});
243+
let access_token = response?;
244+
Ok(IdpServerResponse {
245+
access_token,
246+
expires: None,
247+
refresh_token: None,
248+
})
249+
}
250+
.boxed()
251+
},
252+
CallbackKind::Machine,
253+
),
254+
cache: Cache::new(),
255+
}
256+
}
217257
}
218258

219259
/// The OIDC state containing the cache of necessary OIDC info as well as the function
@@ -467,25 +507,31 @@ pub(crate) async fn reauthenticate_stream(
467507
authenticate_stream(conn, credential, server_api, None).await
468508
}
469509

470-
#[cfg(feature = "azure-oidc")]
471-
async fn setup_automatic_providers(credential: &Credential, state: &mut Option<CallbackInner>) {
510+
#[cfg(any(feature = "azure-oidc", feature = "gcp-oidc"))]
511+
async fn setup_automatic_providers(credential: &Credential, callback: &mut Option<CallbackInner>) {
472512
// If there is already a function, there is no need to set up an automatic provider
473513
// this could happen in the case of a reauthentication, or if the user has already set up
474514
// a function. A situation where the user has set up a function and an automatic provider
475515
// would already have caused an InvalidArgument error in `validate_credential`.
476-
if state.is_some() {
516+
if callback.is_some() {
477517
return;
478518
}
479519
if let Some(ref p) = credential.mechanism_properties {
480520
let environment = p.get_str(ENVIRONMENT_PROP_STR).unwrap_or("");
481-
let client_id = credential.username.as_deref();
482521
let resource = p.get_str(TOKEN_RESOURCE_PROP_STR).unwrap_or("");
483522
match environment {
484523
AZURE_ENVIRONMENT_VALUE_STR => {
485-
*state = Some(Callback::azure_callback(client_id, resource))
524+
#[cfg(feature = "azure-oidc")]
525+
{
526+
let client_id = credential.username.as_deref();
527+
*callback = Some(Callback::azure_callback(client_id, resource))
528+
}
486529
}
487530
GCP_ENVIRONMENT_VALUE_STR => {
488-
// TODO RUST-1627: Implement GCP automatic provider
531+
#[cfg(feature = "gcp-oidc")]
532+
{
533+
*callback = Some(Callback::gcp_callback(resource))
534+
}
489535
}
490536
_ => {}
491537
}
@@ -503,7 +549,7 @@ pub(crate) async fn authenticate_stream(
503549
// always matches that in the Credential Cache.
504550
let mut guard = credential.oidc_callback.inner.lock().await;
505551

506-
#[cfg(feature = "azure-oidc")]
552+
#[cfg(any(feature = "azure-oidc", feature = "gcp-oidc"))]
507553
setup_automatic_providers(credential, &mut guard).await;
508554
let CallbackInner {
509555
cache,

src/runtime.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ mod acknowledged_message;
33
feature = "aws-auth",
44
feature = "azure-kms",
55
feature = "gcp-kms",
6-
feature = "azure-oidc"
6+
feature = "azure-oidc",
7+
feature = "gcp-oidc"
78
))]
89
mod http;
910
mod join_handle;
@@ -36,7 +37,8 @@ use crate::{error::Result, options::ServerAddress};
3637
feature = "aws-auth",
3738
feature = "azure-kms",
3839
feature = "gcp-kms",
39-
feature = "azure-oidc"
40+
feature = "azure-oidc",
41+
feature = "gcp-oidc"
4042
))]
4143
pub(crate) use http::HttpClient;
4244
#[cfg(feature = "openssl-tls")]

src/test/spec/oidc.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,3 +1231,48 @@ mod azure {
12311231
Ok(())
12321232
}
12331233
}
1234+
1235+
mod gcp {
1236+
use crate::client::{options::ClientOptions, Client};
1237+
use bson::{doc, Document};
1238+
1239+
#[tokio::test]
1240+
async fn machine_5_4_gcp_with_no_username() -> anyhow::Result<()> {
1241+
get_env_or_skip!("OIDC");
1242+
1243+
let mut opts = ClientOptions::parse(mongodb_uri_single!()).await?;
1244+
opts.credential.as_mut().unwrap().source = None;
1245+
let client = Client::with_options(opts)?;
1246+
client
1247+
.database("test")
1248+
.collection::<Document>("test")
1249+
.find_one(doc! {})
1250+
.await?;
1251+
Ok(())
1252+
}
1253+
1254+
#[tokio::test]
1255+
async fn machine_5_5_token_resource_must_be_set_for_gcp() -> anyhow::Result<()> {
1256+
get_env_or_skip!("OIDC");
1257+
use crate::client::auth::{ENVIRONMENT_PROP_STR, GCP_ENVIRONMENT_VALUE_STR};
1258+
1259+
let mut opts = ClientOptions::parse(mongodb_uri_single!()).await?;
1260+
opts.credential.as_mut().unwrap().source = None;
1261+
opts.credential.as_mut().unwrap().mechanism_properties = Some(doc! {
1262+
ENVIRONMENT_PROP_STR: GCP_ENVIRONMENT_VALUE_STR,
1263+
});
1264+
let client = Client::with_options(opts)?;
1265+
let res = client
1266+
.database("test")
1267+
.collection::<Document>("test")
1268+
.find_one(doc! {})
1269+
.await;
1270+
1271+
assert!(res.is_err());
1272+
assert!(matches!(
1273+
*res.unwrap_err().kind,
1274+
crate::error::ErrorKind::InvalidArgument { .. },
1275+
));
1276+
Ok(())
1277+
}
1278+
}

0 commit comments

Comments
 (0)