diff --git a/source/fundamentals/enterprise-auth.txt b/source/fundamentals/enterprise-auth.txt index e3a5da67..1d7065fa 100644 --- a/source/fundamentals/enterprise-auth.txt +++ b/source/fundamentals/enterprise-auth.txt @@ -108,6 +108,138 @@ a connection string URI by using the following placeholders: let uri = "mongodb://:@/?authSource=$external&authMechanism=PLAIN"; +.. _rust-mongodb-oidc: + +MONGODB-OIDC +------------ + +.. important:: + + The MONGODB-OIDC authentication mechanism requires MongoDB Server + v7.0 or later running on a Linux platform. + +The {+driver-short+} supports OpenID Connect (**OIDC**) authentication for **workload +identities**. A workload identity is an identity you assign to a +software workload, such as an application, service, script, or +container, to authenticate and access other services and resources. + +The following sections describe how to use the MONGODB-OIDC +authentication mechanism to authenticate to various platforms. + +To learn more about the MONGODB-OIDC authentication mechanism, see +:manual:`OpenID Connect Authentication ` and +:manual:`MongoDB Server Parameters ` +in the Server manual. + +.. _rust-mongodb-oidc-azure-imds: + +Azure IMDS +~~~~~~~~~~ + +If your application runs on an Azure VM, or otherwise uses the +`Azure Instance Metadata Service `__ +(IMDS), you can authenticate to MongoDB by using the {+driver-short+}'s +built-in Azure support. + +You can configure OIDC for Azure IMDS by setting the ``mechanism`` field of your +``Credential`` struct to ``AuthMechanism::MongoDbOidc``. This example specifies +the authentication mechanism by using the following placeholders: + +- ``username``: If you're using an Azure managed identity, set this to the + client ID of the managed identity. If you're using a service principal to + represent an enterprise application, set this to the application ID of the + service principal. +- ``mechanism_properties``: Set the ``ENVIRONMENT`` property to ``azure`` and + the ``TOKEN_RESOURCE`` to the value of the audience parameter configured + on your MongoDB deployment. + +The following code example shows how to set these options when creating a +``Client``: + +.. literalinclude:: /includes/fundamentals/code-snippets/enterprise-auth.rs + :language: rust + :dedent: + :start-after: start-azure-imds + :end-before: end-azure-imds + :emphasize-lines: 2-5 + +.. _rust-mongodb-oidc-gcp-imds: + +GCP IMDS +~~~~~~~~ + +If your application runs on a Google Compute Engine VM, or otherwise uses the +`GCP Instance Metadata Service `__, +you can authenticate to MongoDB by using the {+driver-short+}'s built-in GCP +support. + +You can configure OIDC for GCP IMDS by setting the ``mechanism`` field of your +``Credential`` struct to ``AuthMechanism::MongoDbOidc``. Then, specify the +authentication mechanism by setting the following values in the +``mechanism_properties`` field: + +- ``ENVIRONMENT``: Set this property to ``gcp``. +- ``TOKEN_RESOURCE``: Set this property to the value of the audience parameter configured + on your MongoDB deployment. + +The following code example shows how to set these options when creating a +``Client``: + +.. literalinclude:: /includes/fundamentals/code-snippets/enterprise-auth.rs + :language: rust + :dedent: + :start-after: start-gcp-imds + :end-before: end-gcp-imds + :emphasize-lines: 2-4 + +.. _rust-mongodb-oidc-custom-callback: + +Custom Callback +~~~~~~~~~~~~~~~ + +The {+driver-short+} doesn't offer built-in support for all platforms, including +the AWS Elastic Kubernetes Service (EKS). To use OIDC to authenticate against +unsupported platforms, you must define a custom callback function. + +The following code is an example implementation of a custom callback for an EKS +cluster. First, set the ``oidc_callback`` field of your ``Credential`` struct to +``oidc::Callback::machine``. Then, read the access token from a path set in the +``AWS_WEB_IDENTITY_TOKEN_FILE`` environment variable. Finally, set the value of +the ``access_token`` field of the ``IdpServerResponse`` struct. Optionally, set +the values of the ``expires`` and ``refresh_token`` fields. + +.. literalinclude:: /includes/fundamentals/code-snippets/enterprise-auth.rs + :language: rust + :dedent: + :start-after: start-custom-callback-machine + :end-before: end-custom-callback-machine + :emphasize-lines: 3, 5-7 + +When the workforce identity authentication process involves human interaction, +you must configure the client by setting the ``oidc_callback`` field of your +``Credential`` struct to ``oidc::Callback::human`` instead of +``oidc::Callback::machine``. The {+driver-short+} uses the callback in the +following process: + +1. The driver retrieves the Identity Provider Information (IDPInfo) for the + provided username. +#. The callback negotiates with the IDP to obtain an ``access_token``, and any + potential ``refresh_token`` and timeout values, if configured. The callback + returns a ``Result``. + +The following example defines a custom callback to handle workforce identity. To +customize this example for your use case, replace ```` with your own +custom flow. Refer to `Authorization Code Flow with OIDC +`__ +for more details. + +.. literalinclude:: /includes/fundamentals/code-snippets/enterprise-auth.rs + :language: rust + :dedent: + :start-after: start-custom-callback-user + :end-before: end-custom-callback-user + :emphasize-lines: 3 + Additional Information ---------------------- @@ -129,3 +261,6 @@ guide, see the following API documentation: - `ClientOptions <{+api+}/options/struct.ClientOptions.html>`__ - `Client <{+api+}/struct.Client.html>`__ - `with_options() <{+api+}/struct.Client.html#method.with_options>`__ +- `CallbackContext <{+api+}/options/oidc/struct.CallbackContext.html>`__ +- `IdpServerResponse <{+api+}/options/oidc/struct.IdpServerResponse.html>`__ +- `IdpServerInfo <{+api+}/options/oidc/struct.IdpServerInfo.html>`__ diff --git a/source/includes/fundamentals/code-snippets/enterprise-auth.rs b/source/includes/fundamentals/code-snippets/enterprise-auth.rs index 74a8dc24..ae0373da 100644 --- a/source/includes/fundamentals/code-snippets/enterprise-auth.rs +++ b/source/includes/fundamentals/code-snippets/enterprise-auth.rs @@ -1,4 +1,12 @@ -use mongodb::{ bson::doc, options::{ ClientOptions, Credential, AuthMechanism }, Client }; +use mongodb::options::oidc::{self, Callback, CallbackContext, IdpServerResponse}; +use mongodb::{ + bson::doc, + bson::Document, + options::{ClientOptions, Credential, AuthMechanism}, + Client, +}; +use std::error::Error; +use futures::FutureExt; #[tokio::main] async fn main() -> mongodb::error::Result<()> { @@ -17,5 +25,86 @@ async fn main() -> mongodb::error::Result<()> { let client = Client::with_options(client_options)?; // end-ldap + // start-azure-imds + let credential = Credential::builder() + .username("".to_owned()) + .mechanism(AuthMechanism::MongoDbOidc) + .mechanism_properties( + doc! { "ENVIRONMENT": "azure", "TOKEN_RESOURCE": "" } + ) + .build(); + + client_options.credential = Some(credential); + let client = Client::with_options(client_options)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + // end-azure-imds + + // start-gcp-imds + let credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .mechanism_properties( + doc! { "ENVIRONMENT": "gcp", "TOKEN_RESOURCE": "" } + ) + .build(); + + client_options.credential = Some(credential); + let client = Client::with_options(client_options)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + // end-gcp-imds + + // start-custom-callback-machine + let credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + async move { + let token_file_path = std::env::var("AWS_WEB_IDENTITY_TOKEN_FILE").map_err(mongodb::error::Error::custom)?; + let access_token = tokio::fs::read_to_string(token_file_path).await?; + Ok(IdpServerResponse::builder().access_token(access_token).build()) + } + .boxed() + })) + .build() + .into(); + + client_options.credential = Some(credential); + let client = Client::with_options(client_options)?; + + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + // end-custom-callback-machine + + // start-custom-callback-user + let callback = Callback::human(move |context| { + async move { + ""; + todo!("human flow") + } + .boxed() + }); + let credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(callback) + .build(); + client_options.credential = Some(credential); + let client = Client::with_options(client_options)?; + + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + // end-custom-callback-user + Ok(()) }