Skip to content

DOCSP-45153 Add MONGODB-OIDC #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions source/fundamentals/enterprise-auth.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,138 @@ a connection string URI by using the following placeholders:

let uri = "mongodb://<username>:<password>@<hostname>/?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 </core/security-oidc/>` and
:manual:`MongoDB Server Parameters </reference/parameters/#mongodb-parameter-param.oidcIdentityProviders>`
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 <https://learn.microsoft.com/en-us/azure/virtual-machines/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 <https://cloud.google.com/compute/docs/metadata/querying-metadata>`__,
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<IdpServerInfo>``.

The following example defines a custom callback to handle workforce identity. To
customize this example for your use case, replace ``<human flow>`` with your own
custom flow. Refer to `Authorization Code Flow with OIDC
<https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-auth-code-flow>`__
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
----------------------

Expand All @@ -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>`__
91 changes: 90 additions & 1 deletion source/includes/fundamentals/code-snippets/enterprise-auth.rs
Original file line number Diff line number Diff line change
@@ -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<()> {
Expand All @@ -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("<username>".to_owned())
.mechanism(AuthMechanism::MongoDbOidc)
.mechanism_properties(
doc! { "ENVIRONMENT": "azure", "TOKEN_RESOURCE": "<audience>" }
)
.build();

client_options.credential = Some(credential);
let client = Client::with_options(client_options)?;
let res = client
.database("test")
.collection::<Document>("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": "<audience>" }
)
.build();

client_options.credential = Some(credential);
let client = Client::with_options(client_options)?;
let res = client
.database("test")
.collection::<Document>("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::<Document>("test")
.find_one(doc! {})
.await?;
// end-custom-callback-machine

// start-custom-callback-user
let callback = Callback::human(move |context| {
async move {
"<human flow>";
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::<Document>("test")
.find_one(doc! {})
.await?;
// end-custom-callback-user

Ok(())
}
Loading