Skip to content

Commit

Permalink
RUST-1627: Add automatic token acquisition for GCP (#1097)
Browse files Browse the repository at this point in the history
  • Loading branch information
pmeredit authored Jun 4, 2024
1 parent 233d695 commit 6e4126a
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 27 deletions.
89 changes: 77 additions & 12 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ buildvariants:
tasks:
- testoidc_task_group
- testazureoidc_task_group
- testgcpoidc_task_group

- name: oidc-macos
display_name: "OIDC Macos"
Expand All @@ -331,6 +332,7 @@ buildvariants:
tasks:
- testoidc_task_group
- testazureoidc_task_group
- testgcpoidc_task_group

- name: in-use-encryption
display_name: "In-Use Encryption"
Expand Down Expand Up @@ -653,14 +655,13 @@ task_groups:
- func: fix absolute paths
- func: init test-results
- func: make files executable
- command: shell.exec
- command: subprocess.exec
params:
shell: bash
binary: bash
env:
AZUREOIDC_VMNAME_PREFIX: "RUST_DRIVER"
script: |
${PREPARE_SHELL}
${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/create-and-setup-vm.sh
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/create-and-setup-vm.sh
teardown_task:
- command: subprocess.exec
params:
Expand All @@ -672,6 +673,32 @@ task_groups:
tasks:
- oidc-auth-test-azure-latest

- name: testgcpoidc_task_group
setup_group:
- func: fetch source
- func: create expansions
- func: prepare resources
- func: fix absolute paths
- func: init test-results
- func: make files executable
- command: subprocess.exec
params:
binary: bash
env:
GCPOIDC_VMNAME_PREFIX: "RUST_DRIVER"
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh
teardown_task:
- command: subprocess.exec
params:
binary: bash
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/teardown.sh
setup_group_can_fail_task: true
setup_group_timeout_secs: 1800
tasks:
- oidc-auth-test-gcp-latest

#########
# Tasks #
#########
Expand Down Expand Up @@ -1081,15 +1108,53 @@ tasks:
script: |-
set -o errexit
${PREPARE_SHELL}
git add .
git commit -m "add files"
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tgz
git archive -o $AZUREOIDC_DRIVERS_TAR_FILE HEAD
export AZUREOIDC_TEST_CMD="PROJECT_DIRECTORY='.' ./.evergreen/install-dependencies.sh rust\
&& PROJECT_DIRECTORY='.' .evergreen/install-dependencies.sh junit-dependencies\
&& PROJECT_DIRECTORY='.' OIDC_ENV=azure OIDC=oidc ./.evergreen/run-mongodb-oidc-test.sh"
./.evergreen/install-dependencies.sh rust
source .cargo/env
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tar
rustup default stable
export RUSTFLAGS="-C target-feature=+crt-static"
cargo test --features azure-oidc --target x86_64-unknown-linux-gnu get_exe_name -- --ignored
export TEST_FILE=$(cat exe_name.txt)
rm "$AZUREOIDC_DRIVERS_TAR_FILE" || true
tar -cf $AZUREOIDC_DRIVERS_TAR_FILE $TEST_FILE
tar -uf $AZUREOIDC_DRIVERS_TAR_FILE ./.evergreen
rm "$AZUREOIDC_DRIVERS_TAR_FILE".gz || true
gzip $AZUREOIDC_DRIVERS_TAR_FILE
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tar.gz
# Define the command to run on the azure VM.
# Ensure that we source the environment file created for us, set up any other variables we need,
# and then run our test suite on the vm.
export AZUREOIDC_TEST_CMD="ls -laR data && PROJECT_DIRECTORY='.' OIDC_ENV=azure OIDC=oidc TEST_FILE=./$TEST_FILE ./.evergreen/run-mongodb-oidc-test.sh"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh
- name: "oidc-auth-test-gcp-latest"
commands:
- command: shell.exec
params:
working_dir: src
shell: bash
script: |-
set -o errexit
${PREPARE_SHELL}
./.evergreen/install-dependencies.sh rust
source .cargo/env
export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tar
rustup default stable
export RUSTFLAGS="-C target-feature=+crt-static"
cargo test --features gcp-oidc --target x86_64-unknown-linux-gnu test::atlas_planned_maintenance_testing::get_exe_name -- --ignored
export TEST_FILE=$(cat exe_name.txt)
rm "$GCPOIDC_DRIVERS_TAR_FILE" || true
tar -cf $GCPOIDC_DRIVERS_TAR_FILE $TEST_FILE
tar -uf $GCPOIDC_DRIVERS_TAR_FILE ./.evergreen
rm "$GCPOIDC_DRIVERS_TAR_FILE".gz || true
gzip $GCPOIDC_DRIVERS_TAR_FILE
export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-rust-driver.tar.gz
# Define the command to run on the gcp VM.
# Ensure that we source the environment file created for us, set up any other variables we need,
# and then run our test suite on the vm.
export GCPOIDC_TEST_CMD="ls -la && PROJECT_DIRECTORY='.' OIDC_ENV=gcp OIDC=oidc TEST_FILE=./$TEST_FILE ./.evergreen/run-mongodb-oidc-test.sh"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh
#############
# Functions #
#############
Expand Down
15 changes: 10 additions & 5 deletions .evergreen/run-mongodb-oidc-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
set +x # Disable debug trace
set -o errexit # Exit the script with error if any of the commands fail

source .evergreen/env.sh
source .evergreen/cargo-test.sh

echo "Running MONGODB-OIDC authentication tests"

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

if [ $OIDC_ENV == "test" ]; then

source .evergreen/env.sh
source .evergreen/cargo-test.sh
# Make sure DRIVERS_TOOLS is set.
if [ -z "$DRIVERS_TOOLS" ]; then
echo "Must specify DRIVERS_TOOLS"
Expand All @@ -24,15 +24,20 @@ if [ $OIDC_ENV == "test" ]; then

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

cargo nextest run test::spec::oidc::azure --no-capture --profile ci --features=azure-oidc
$TEST_FILE test::spec::oidc::azure --nocapture
RESULT=$?
elif [ $OIDC_ENV == "gcp" ]; then
source ./secrets-export.sh

$TEST_FILE test::spec::oidc::gcp --nocapture
RESULT=$?
else
echo "Unrecognized OIDC_ENV $OIDC_ENV"
exit 1
fi

cp target/nextest/ci/junit.xml results.xml
exit $RESULT
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ authors = [
"Isabel Atkinson <isabel.atkinson@mongodb.com>",
"Abraham Egnor <abraham.egnor@mongodb.com>",
"Kaitlin Mahar <kaitlin.mahar@mongodb.com>",
"Patrick Meredith <pmeredit@protonmail.com>",
]
description = "The official MongoDB driver for Rust"
edition = "2021"
Expand Down Expand Up @@ -45,6 +46,9 @@ azure-kms = ["dep:reqwest"]
# Enable support for azure OIDC authentication.
azure-oidc = ["dep:reqwest"]

# Enable support for gcp OIDC authentication.
gcp-oidc = ["dep:reqwest"]

# Enable support for on-demand GCP KMS credentials.
# This can only be used with the tokio-runtime feature flag.
gcp-kms = ["dep:reqwest"]
Expand Down
62 changes: 54 additions & 8 deletions src/client/auth/oidc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{
use tokio::sync::Mutex;
use typed_builder::TypedBuilder;

#[cfg(feature = "azure-oidc")]
#[cfg(any(feature = "azure-oidc", feature = "gcp-oidc"))]
use crate::client::auth::{
AZURE_ENVIRONMENT_VALUE_STR,
ENVIRONMENT_PROP_STR,
Expand Down Expand Up @@ -214,6 +214,46 @@ impl Callback {
cache: Cache::new(),
}
}

/// Create gcp callback.
#[cfg(feature = "gcp-oidc")]
fn gcp_callback(resource: &str) -> CallbackInner {
use futures_util::FutureExt;
let url = format!(
"http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience={}",
resource
);
CallbackInner {
function: Self::new_function(
move |_| {
let url = url.clone();
async move {
let url = url.clone();
let response = crate::runtime::HttpClient::default()
.get(&url)
.headers(&[("Metadata-Flavor", "Google")])
.send_and_get_string()
.await
.map_err(|e| {
Error::authentication_error(
MONGODB_OIDC_STR,
&format!("Failed to get access token from GCP IDMS: {}", e),
)
});
let access_token = response?;
Ok(IdpServerResponse {
access_token,
expires: None,
refresh_token: None,
})
}
.boxed()
},
CallbackKind::Machine,
),
cache: Cache::new(),
}
}
}

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

#[cfg(feature = "azure-oidc")]
async fn setup_automatic_providers(credential: &Credential, state: &mut Option<CallbackInner>) {
#[cfg(any(feature = "azure-oidc", feature = "gcp-oidc"))]
async fn setup_automatic_providers(credential: &Credential, callback: &mut Option<CallbackInner>) {
// If there is already a function, there is no need to set up an automatic provider
// this could happen in the case of a reauthentication, or if the user has already set up
// a function. A situation where the user has set up a function and an automatic provider
// would already have caused an InvalidArgument error in `validate_credential`.
if state.is_some() {
if callback.is_some() {
return;
}
if let Some(ref p) = credential.mechanism_properties {
let environment = p.get_str(ENVIRONMENT_PROP_STR).unwrap_or("");
let client_id = credential.username.as_deref();
let resource = p.get_str(TOKEN_RESOURCE_PROP_STR).unwrap_or("");
match environment {
AZURE_ENVIRONMENT_VALUE_STR => {
*state = Some(Callback::azure_callback(client_id, resource))
#[cfg(feature = "azure-oidc")]
{
let client_id = credential.username.as_deref();
*callback = Some(Callback::azure_callback(client_id, resource))
}
}
GCP_ENVIRONMENT_VALUE_STR => {
// TODO RUST-1627: Implement GCP automatic provider
#[cfg(feature = "gcp-oidc")]
{
*callback = Some(Callback::gcp_callback(resource))
}
}
_ => {}
}
Expand All @@ -503,7 +549,7 @@ pub(crate) async fn authenticate_stream(
// always matches that in the Credential Cache.
let mut guard = credential.oidc_callback.inner.lock().await;

#[cfg(feature = "azure-oidc")]
#[cfg(any(feature = "azure-oidc", feature = "gcp-oidc"))]
setup_automatic_providers(credential, &mut guard).await;
let CallbackInner {
cache,
Expand Down
6 changes: 4 additions & 2 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ mod acknowledged_message;
feature = "aws-auth",
feature = "azure-kms",
feature = "gcp-kms",
feature = "azure-oidc"
feature = "azure-oidc",
feature = "gcp-oidc"
))]
mod http;
mod join_handle;
Expand Down Expand Up @@ -36,7 +37,8 @@ use crate::{error::Result, options::ServerAddress};
feature = "aws-auth",
feature = "azure-kms",
feature = "gcp-kms",
feature = "azure-oidc"
feature = "azure-oidc",
feature = "gcp-oidc"
))]
pub(crate) use http::HttpClient;
#[cfg(feature = "openssl-tls")]
Expand Down
45 changes: 45 additions & 0 deletions src/test/spec/oidc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1231,3 +1231,48 @@ mod azure {
Ok(())
}
}

mod gcp {
use crate::client::{options::ClientOptions, Client};
use bson::{doc, Document};

#[tokio::test]
async fn machine_5_4_gcp_with_no_username() -> anyhow::Result<()> {
get_env_or_skip!("OIDC");

let mut opts = ClientOptions::parse(mongodb_uri_single!()).await?;
opts.credential.as_mut().unwrap().source = None;
let client = Client::with_options(opts)?;
client
.database("test")
.collection::<Document>("test")
.find_one(doc! {})
.await?;
Ok(())
}

#[tokio::test]
async fn machine_5_5_token_resource_must_be_set_for_gcp() -> anyhow::Result<()> {
get_env_or_skip!("OIDC");
use crate::client::auth::{ENVIRONMENT_PROP_STR, GCP_ENVIRONMENT_VALUE_STR};

let mut opts = ClientOptions::parse(mongodb_uri_single!()).await?;
opts.credential.as_mut().unwrap().source = None;
opts.credential.as_mut().unwrap().mechanism_properties = Some(doc! {
ENVIRONMENT_PROP_STR: GCP_ENVIRONMENT_VALUE_STR,
});
let client = Client::with_options(opts)?;
let res = client
.database("test")
.collection::<Document>("test")
.find_one(doc! {})
.await;

assert!(res.is_err());
assert!(matches!(
*res.unwrap_err().kind,
crate::error::ErrorKind::InvalidArgument { .. },
));
Ok(())
}
}

0 comments on commit 6e4126a

Please sign in to comment.