diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec2a89ef6e..eb873a9e54 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,8 +40,8 @@ repos: rev: "v2.2.6" hooks: - id: codespell - args: ["-L", "te,fo,fle,alo,nin,compres,wil,collone,asess,sav,ot,wll,dne,nulll,hellow"] - exclude: ^(vendor/|internal/cmd/benchmark/operation_test.go|bson/testdata/lorem.txt) + args: ["-L", "te,fo,fle,alo,nin,compres,wil,collone,asess,sav,ot,wll,dne,nulll,hellow,aks"] + exclude: ^(vendor/|internal/cmd/benchmark/operation_test.go|bson/testdata/) exclude_types: [json,yaml,pem] - repo: https://github.com/tcort/markdown-link-check diff --git a/mongo/client_examples_test.go b/mongo/client_examples_test.go index 2ae670f282..0fa52f55d8 100644 --- a/mongo/client_examples_test.go +++ b/mongo/client_examples_test.go @@ -10,6 +10,7 @@ import ( "context" "fmt" "log" + "os" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" @@ -459,3 +460,267 @@ 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 "" below is the client id of the Azure managed identity, + // and ```` is the url-encoded ``audience`` `configured on your + // MongoDB deployment`. + { + uri := os.Getenv("MONGODB_URI") + props := map[string]string{ + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "", + } + opts := options.Client().ApplyURI(uri) + opts.SetAuth( + options.Credential{ + Username: "", + AuthMechanism: "MONGODB-OIDC", + AuthMechanismProperties: props, + }, + ) + c, err := mongo.Connect(opts) + if err != nil { + panic(err) + } + defer func() { _ = c.Disconnect(context.TODO()) }() + _, err = c.Database("test"). + Collection("test"). + InsertOne(context.TODO(), bson.D{}) + if err != nil { + panic(err) + } + } + + // 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 "" below is the url-encoded "audience" `configured on + // your MongoDB deployment`. + { + uri := os.Getenv("MONGODB_URI") + props := map[string]string{ + "ENVIRONMENT": "gcp", + "TOKEN_RESOURCE": "", + } + opts := options.Client().ApplyURI(uri) + opts.SetAuth( + options.Credential{ + AuthMechanism: "MONGODB-OIDC", + AuthMechanismProperties: props, + }, + ) + c, err := mongo.Connect(opts) + if err != nil { + panic(err) + } + defer func() { _ = c.Disconnect(context.TODO()) }() + _, err = c.Database("test"). + Collection("test"). + InsertOne(context.TODO(), bson.D{}) + if err != nil { + panic(err) + } + } + + // 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": "", + } + opts := options.Client().ApplyURI(uri) + opts.SetAuth( + options.Credential{ + AuthMechanism: "MONGODB-OIDC", + AuthMechanismProperties: props, + OIDCMachineCallback: eksCallback, + }, + ) + c, err := mongo.Connect(opts) + if err != nil { + panic(err) + } + defer func() { _ = c.Disconnect(context.TODO()) }() + _, err = c.Database("test"). + Collection("test"). + InsertOne(context.TODO(), bson.D{}) + if err != nil { + panic(err) + } + } + + // 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. + + // 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": "", + } + opts := options.Client().ApplyURI(uri) + opts.SetAuth( + options.Credential{ + AuthMechanism: "MONGODB-OIDC", + AuthMechanismProperties: props, + OIDCMachineCallback: gkeCallback, + }, + ) + c, err := mongo.Connect(opts) + if err != nil { + panic(err) + } + defer func() { _ = c.Disconnect(context.TODO()) }() + _, err = c.Database("test"). + Collection("test"). + InsertOne(context.TODO(), bson.D{}) + if err != nil { + panic(err) + } + } + + // 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 Provider (IDP) + // Information (IDPInfo) for the passed username. The OIDCHumanCallback then + // needs to 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 permissions to the IdP. + accessToken, err := negotiateWithIDP(ctx, idpInfo.Issuer) + if err != nil { + return nil, err + } + return &options.OIDCCredential{ + AccessToken: accessToken, + }, nil + } + uri := os.Getenv("MONGODB_URI") + props := map[string]string{ + "ENVIRONMENT": "gcp", + "TOKEN_RESOURCE": "", + } + opts := options.Client().ApplyURI(uri) + opts.SetAuth( + options.Credential{ + AuthMechanism: "MONGODB-OIDC", + AuthMechanismProperties: props, + OIDCHumanCallback: humanCallback, + }, + ) + c, err := mongo.Connect(opts) + if err != nil { + panic(err) + } + defer func() { _ = c.Disconnect(context.TODO()) }() + _, err = c.Database("test"). + Collection("test"). + InsertOne(context.TODO(), bson.D{}) + if err != nil { + panic(err) + } + } + + // * 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 +}