Skip to content
199 changes: 199 additions & 0 deletions mongo/client_examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"fmt"
"log"
"os"

"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
Expand Down Expand Up @@ -468,3 +469,201 @@ func ExampleConnect_bSONOptions() {
panic(err)
}
}

func ExampleConnect_oIDC() {
// The `MONGODB-OIDC authentication mechanism`_ is available in MongoDB 7.0+ on Linux platforms.
//
// The MONGODB-OIDC mechanism authenticates using an OpenID Connect (OIDC) access token.
// The driver supports OIDC for workload identity, defined as 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 driver also supports OIDC for workforce identity for a more secure flow with a human in
// the loop.

// Credentials can be configured through the MongoDB URI or as arguments in the
// options.ClientOptions struct that is passed into the mongo.Connect function.

// Built-in Support
// The driver has built-in support for Azure IMDS and GCP IMDS environments. Other environments
// are supported with `Custom Callbacks`_.

// Azure IMDS
// For an application running on an Azure VM or otherwise using the `Azure Internal Metadata Service`_,
// you can use the built-in support for Azure, where "<client_id>" below is the client id of the Azure
// managed identity, and ``<audience>`` is the url-encoded ``audience`` `configured on your MongoDB deployment`_.
{
uri := os.Getenv("MONGODB_URI")
props := map[string]string{"ENVIRONMENT": "azure", "TOKEN_RESOURCE": "<audience>"}
opts := options.Client().ApplyURI(uri)
opts.SetAuth(
options.Credential{
Username: "<client_id>",
AuthMechanism: "MONGODB-OIDC",
AuthMechanismProperties: props,
},
)
c, err := mongo.Connect(context.TODO(), opts)
if err != nil {
panic(err)
}
defer c.Disconnect(context.TODO())
c.Database("test").Collection("test").InsertOne(context.TODO(), bson.D{})
}

// If the application is running on an Azure VM and only one managed identity is associated with the
// VM, "username" can be omitted.

// GCP IMDS

// For an application running on an GCP VM or otherwise using the `GCP Internal Metadata Service`_,
// you can use the built-in support for GCP, where "<audience>" below is the url-encoded "audience"
// `configured on your MongoDB deployment`_.
{
uri := os.Getenv("MONGODB_URI")
props := map[string]string{"ENVIRONMENT": "gcp", "TOKEN_RESOURCE": "<audience>"}
opts := options.Client().ApplyURI(uri)
opts.SetAuth(
options.Credential{
AuthMechanism: "MONGODB-OIDC",
AuthMechanismProperties: props,
},
)
c, err := mongo.Connect(context.TODO(), opts)
if err != nil {
panic(err)
}
defer c.Disconnect(context.TODO())
c.Database("test").Collection("test").InsertOne(context.TODO(), bson.D{})
}

// Custom Callbacks

// For environments that are not directly supported by the driver, you can use
// options.OIDCCallback.
// Some examples are given below.

// AWS EKS

// For an EKS Cluster with a configured `IAM OIDC provider`_, the token can be read from a path given by
// the "AWS_WEB_IDENTITY_TOKEN_FILE" environment variable.
{
eksCallback := func(_ context.Context, _ *options.OIDCArgs) (*options.OIDCCredential, error) {
accessToken, err := os.ReadFile(os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE"))
if err != nil {
return nil, err
}
return &options.OIDCCredential{
AccessToken: string(accessToken),
}, nil
}
uri := os.Getenv("MONGODB_URI")
props := map[string]string{"ENVIRONMENT": "gcp", "TOKEN_RESOURCE": "<audience>"}
opts := options.Client().ApplyURI(uri)
opts.SetAuth(
options.Credential{
AuthMechanism: "MONGODB-OIDC",
AuthMechanismProperties: props,
OIDCMachineCallback: eksCallback,
},
)
c, err := mongo.Connect(context.TODO(), opts)
if err != nil {
panic(err)
}
defer c.Disconnect(context.TODO())
c.Database("test").Collection("test").InsertOne(context.TODO(), bson.D{})
}

// Other Azure Environments

// For applications running on Azure Functions, App Service Environment (ASE), or
// Azure Kubernetes Service (AKS), you can use the `azidentity package`
// (https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity)
// to fetch the credentials. In each case, the OIDCCallback function should return
// the AccessToken from the azidentity package.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how to actually add an example for this because I didn't want to add the azidentity package to the go driver go.mod file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think what you have makes sense.


// GCP GKE

// For a Google Kubernetes Engine cluster with a `configured service account`_, the token can be read from the standard
// service account token file location.
{
gkeCallback := func(_ context.Context, _ *options.OIDCArgs) (*options.OIDCCredential, error) {
accessToken, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
if err != nil {
return nil, err
}
return &options.OIDCCredential{
AccessToken: string(accessToken),
}, nil
}
uri := os.Getenv("MONGODB_URI")
props := map[string]string{"ENVIRONMENT": "gcp", "TOKEN_RESOURCE": "<audience>"}
opts := options.Client().ApplyURI(uri)
opts.SetAuth(
options.Credential{
AuthMechanism: "MONGODB-OIDC",
AuthMechanismProperties: props,
OIDCMachineCallback: gkeCallback,
},
)
c, err := mongo.Connect(context.TODO(), opts)
if err != nil {
panic(err)
}
defer c.Disconnect(context.TODO())
c.Database("test").Collection("test").InsertOne(context.TODO(), bson.D{})
}

// For workforce identity, the Client must be configured with the OIDCHumanCallback rather than
// the OIDCMachineCallback. The OIDCHumanCallback is used by the driver in a process that is
// two step. In the first step, the driver retrieves the Identity Prodiver (IDP) Information (IDPInfo) for the
// passed username. The OIDCHumanCallback then needs negotiate with the IDP in order to obtain
// an AccessToken, possible RefreshToken, any timeouts, and return them, similar to the OIDCMachineCallbacks seen above.
// See https://docs.hidglobal.com/dev/auth-service/integration/openid-authentication-flows.html
// for more information on various OIDC authentication flows.
{
humanCallback := func(ctx context.Context, opts *options.OIDCArgs) (*options.OIDCCredential, error) {
// idpInfo passed from the driver by asking the MongoDB server for the info configured
// for the username
idpInfo := opts.IDPInfo
// negotiateWithIDP must work with the IdP to obtain an access token. In many cases this
// will involve opening a webbrowser or providing a URL on the command line to a
// human-in-the-loop who can give persmissions to the IdP.
accessToken, err := negotiateWithIDP(ctx, idpInfo.Issuer)
if err != nil {
return nil, err
}
return &options.OIDCCredential{
AccessToken: string(accessToken),
}, nil
}
uri := os.Getenv("MONGODB_URI")
props := map[string]string{"ENVIRONMENT": "gcp", "TOKEN_RESOURCE": "<audience>"}
opts := options.Client().ApplyURI(uri)
opts.SetAuth(
options.Credential{
AuthMechanism: "MONGODB-OIDC",
AuthMechanismProperties: props,
OIDCHumanCallback: humanCallback,
},
)
c, err := mongo.Connect(context.TODO(), opts)
if err != nil {
panic(err)
}
defer c.Disconnect(context.TODO())
c.Database("test").Collection("test").InsertOne(context.TODO(), bson.D{})
}

// * MONGODB-OIDC authentication mechanism: https://www.mongodb.com/docs/manual/core/security-oidc/
// * OIDC Identity Provider Configuration: https://www.mongodb.com/docs/manual/reference/parameters/#mongodb-parameter-param.oidcIdentityProviders
// * Azure Internal Metadata Service: https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service
// * GCP Internal Metadata Service: https://cloud.google.com/compute/docs/metadata/querying-metadata
// * IAM OIDC provider: https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html
// * azure-identity package: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity
// * configured service account: https://cloud.google.com/kubernetes-engine/docs/how-to/service-accounts
}

func negotiateWithIDP(_ context.Context, _ string) (string, error) {
return "", nil
}