Skip to content
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

GODRIVER-2758: Add documentation examples #1759

Merged
merged 12 commits into from
Sep 11, 2024
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() {
pmeredit marked this conversation as resolved.
Show resolved Hide resolved
// 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
}
Loading