diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 722545f288..903e5b9267 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -162,17 +162,18 @@ functions: args: - ${DRIVERS_TOOLS}/.evergreen/teardown.sh - assume-ec2-role: + assume-test-secrets-ec2-role: - command: ec2.assume_role params: role_arn: ${aws_test_secrets_role} + duration_seconds: 1800 run-oidc-auth-test-with-test-credentials: - command: subprocess.exec type: test params: binary: bash - env: + env: OIDC: oidc include_expansions_in_env: [DRIVERS_TOOLS, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] args: [*task-runner, test-oidc] @@ -396,6 +397,16 @@ functions: include_expansions_in_env: ["MONGODB_URI"] args: [*task-runner, test-goleak] + "run oidc k8s test": + - command: subprocess.exec + type: test + params: + binary: bash + include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, VARIANT, DRIVERS_TOOLS] + env: + OIDC_ENV: k8s + args: [*task-runner, test-oidc-remote] + run-ocsp-server: - command: subprocess.exec params: @@ -1658,6 +1669,19 @@ tasks: OIDC_ENV: gcp args: [*task-runner, test-oidc-remote] + - name: "oidc-auth-test-k8s" + commands: + - func: assume-test-secrets-ec2-role + - func: "run oidc k8s test" + vars: + VARIANT: eks + - func: "run oidc k8s test" + vars: + VARIANT: gke + - func: "run oidc k8s test" + vars: + VARIANT: aks + - name: "test-search-index" commands: - func: "bootstrap-mongo-orchestration" @@ -1902,14 +1926,17 @@ task_groups: - ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/teardown.sh - func: teardown - func: handle-test-artifacts - tasks: - testazurekms-task - name: testoidc_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + teardown_task_can_fail_task: true + teardown_group_timeout_secs: 180 # 3 minutes (max allowed time) setup_group: - func: setup-system - - func: assume-ec2-role + - func: assume-test-secrets-ec2-role - command: subprocess.exec params: binary: bash @@ -1926,12 +1953,14 @@ task_groups: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/teardown.sh - func: teardown - func: handle-test-artifacts - setup_group_can_fail_task: true - setup_group_timeout_secs: 1800 tasks: - oidc-auth-test - name: testazureoidc_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + teardown_task_can_fail_task: true + teardown_group_timeout_secs: 180 # 3 minutes (max allowed time) setup_group: - func: setup-system - command: subprocess.exec @@ -1949,12 +1978,14 @@ task_groups: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/teardown.sh - func: teardown - func: handle-test-artifacts - setup_group_can_fail_task: true - setup_group_timeout_secs: 1800 tasks: - oidc-auth-test-azure - name: testgcpoidc_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + teardown_task_can_fail_task: true + teardown_group_timeout_secs: 180 # 3 minutes (max allowed time) setup_group: - func: setup-system - command: subprocess.exec @@ -1972,10 +2003,32 @@ task_groups: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/teardown.sh - func: teardown - func: handle-test-artifacts + tasks: + - oidc-auth-test-gcp + + - name: testk8soidc_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 + teardown_task_can_fail_task: true + teardown_group_timeout_secs: 180 # 3 minutes (max allowed time) + setup_group: + - func: setup-system + - command: subprocess.exec + params: + binary: bash + include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh + teardown_group: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh + - func: teardown + - func: handle-test-artifacts tasks: - - oidc-auth-test-gcp + - oidc-auth-test-k8s - name: test-aws-lambda-task-group setup_group: @@ -2322,8 +2375,6 @@ buildvariants: GO_DIST: "/opt/golang/go1.22" tasks: - name: testoidc_task_group - batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README - name: testazureoidc_task_group - batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README - name: testgcpoidc_task_group - batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - name: testk8soidc_task_group diff --git a/etc/run-oidc-remote-test.sh b/etc/run-oidc-remote-test.sh index ee958c22fc..925754d8f0 100755 --- a/etc/run-oidc-remote-test.sh +++ b/etc/run-oidc-remote-test.sh @@ -31,6 +31,14 @@ elif [ $OIDC_ENV == "gcp" ]; then export GCPOIDC_TEST_CMD="PROJECT_DIRECTORY='.' OIDC_ENV=gcp OIDC=oidc ./etc/run-oidc-test.sh ./test" bash ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/run-driver-test.sh +elif [ $OIDC_ENV == "k8s" ]; then + export K8S_VARIANT=${VARIANT} + export K8S_DRIVERS_TAR_FILE=$DRIVERS_TAR_FILE + export K8S_TEST_CMD="PROJECT_DIRECTORY='.' OIDC_ENV=k8s OIDC=oidc ./etc/run-oidc-test.sh ./test" + bash ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup-pod.sh + bash ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/run-driver-test.sh + bash ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown-pod.sh + else echo "Unrecognized OIDC_ENV $OIDC_ENV" exit 1 diff --git a/etc/run-oidc-test.sh b/etc/run-oidc-test.sh index 4548a124a1..48c273bb26 100755 --- a/etc/run-oidc-test.sh +++ b/etc/run-oidc-test.sh @@ -21,6 +21,12 @@ elif [ $OIDC_ENV == "azure" ]; then elif [ $OIDC_ENV == "gcp" ]; then source ./secrets-export.sh +elif [ $OIDC_ENV == "k8s" ]; then + # "run-driver-test.sh" in drivers-evergreen-tools takes care of sourcing + # "secrets-export.sh". Nothing to do in this block, but we still need a + # command to be syntactically valid, so use no-op command ":". + : + else echo "Unrecognized OIDC_ENV $OIDC_ENV" exit 1 diff --git a/internal/cmd/testoidcauth/main.go b/internal/cmd/testoidcauth/main.go index ecc07d88a2..a795d43717 100644 --- a/internal/cmd/testoidcauth/main.go +++ b/internal/cmd/testoidcauth/main.go @@ -13,6 +13,7 @@ import ( "os" "path" "reflect" + "strings" "sync" "time" "unsafe" @@ -101,7 +102,7 @@ func main() { hasError := false aux := func(test_name string, f func() error) { - fmt.Printf("%s...", test_name) + fmt.Printf("%s...\n", test_name) err := f() if err != nil { fmt.Println("Test Error: ", err) @@ -148,6 +149,8 @@ func main() { aux("machine_5_2_azureWithNoUsername", machine52azureWithBadUsername) case "gcp": aux("machine_6_1_gcpWithNoUsername", machine61gcpWithNoUsername) + case "k8s": + aux("machine_k8s", machinek8s) default: log.Fatal("Unknown OIDC_ENV: ", env) } @@ -282,7 +285,7 @@ func machine21validCallbackInputs() error { tokenFile := tokenFile("test_user1") accessToken, err := os.ReadFile(tokenFile) if err != nil { - fmt.Printf("machine_2_1: failed reading token file: %v", err) + return nil, fmt.Errorf("machine_2_1: failed reading token file: %w", err) } return &options.OIDCCredential{ AccessToken: string(accessToken), @@ -1817,3 +1820,27 @@ func machine61gcpWithNoUsername() error { } return nil } + +// machinek8s tests the "k8s" Kubernetes OIDC environment. There is no specified +// prose test for "k8s", so this test simply checks that you can run a "find" +// with the provided conn string. +func machinek8s() error { + if !strings.Contains(uriSingle, "ENVIRONMENT:k8s") { + return fmt.Errorf("expected MONGODB_URI_SINGLE to specify ENVIRONMENT:k8s for Kubernetes test") + } + + opts := options.Client().ApplyURI(uriSingle) + client, err := mongo.Connect(opts) + if err != nil { + return fmt.Errorf("machine_k8s: failed connecting client: %v", err) + } + defer func() { _ = client.Disconnect(context.Background()) }() + + coll := client.Database("test").Collection("test") + + _, err = coll.Find(context.Background(), bson.D{}) + if err != nil { + return fmt.Errorf("machine_k8s: failed executing Find: %v", err) + } + return nil +} diff --git a/mongo/options/clientoptions.go b/mongo/options/clientoptions.go index 304c69835d..9570d44510 100644 --- a/mongo/options/clientoptions.go +++ b/mongo/options/clientoptions.go @@ -596,24 +596,45 @@ func (c *ClientOptions) Validate() error { return fmt.Errorf("cannot set both OIDCMachineCallback and OIDCHumanCallback, only one may be specified") } if c.Auth.OIDCHumanCallback == nil && c.Auth.AuthMechanismProperties[auth.AllowedHostsProp] != "" { - return fmt.Errorf("Cannot specify ALLOWED_HOSTS without an OIDCHumanCallback") + return fmt.Errorf("cannot specify ALLOWED_HOSTS without an OIDCHumanCallback") } + if c.Auth.OIDCMachineCallback == nil && c.Auth.OIDCHumanCallback == nil && c.Auth.AuthMechanismProperties[auth.EnvironmentProp] == "" { + return errors.New("must specify at least one of OIDCMachineCallback, OIDCHumanCallback, or ENVIRONMENT authMechanismProperty") + } + + // Return an error if an unsupported authMechanismProperty is specified + // for MONGODB-OIDC. + for prop := range c.Auth.AuthMechanismProperties { + switch prop { + case auth.AllowedHostsProp, auth.EnvironmentProp, auth.ResourceProp: + default: + return fmt.Errorf("auth mechanism property %q is not valid for MONGODB-OIDC", prop) + } + } + if env, ok := c.Auth.AuthMechanismProperties[auth.EnvironmentProp]; ok { switch env { case auth.GCPEnvironmentValue, auth.AzureEnvironmentValue: + if c.Auth.AuthMechanismProperties[auth.ResourceProp] == "" { + return fmt.Errorf("%q must be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp) + } + fallthrough + case auth.K8SEnvironmentValue: if c.Auth.OIDCMachineCallback != nil { return fmt.Errorf("OIDCMachineCallback cannot be specified with the %s %q", env, auth.EnvironmentProp) } if c.Auth.OIDCHumanCallback != nil { return fmt.Errorf("OIDCHumanCallback cannot be specified with the %s %q", env, auth.EnvironmentProp) } - if c.Auth.AuthMechanismProperties[auth.ResourceProp] == "" { - return fmt.Errorf("%q must be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp) - } - default: + case auth.TestEnvironmentValue: if c.Auth.AuthMechanismProperties[auth.ResourceProp] != "" { return fmt.Errorf("%q must not be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp) } + if c.Auth.Username != "" { + return fmt.Errorf("must not specify username for %s %q", env, auth.EnvironmentProp) + } + default: + return fmt.Errorf("the %s %q is not supported for MONGODB-OIDC", env, auth.EnvironmentProp) } } } diff --git a/mongo/options/clientoptions_test.go b/mongo/options/clientoptions_test.go index 6a384e660f..907584d5f0 100644 --- a/mongo/options/clientoptions_test.go +++ b/mongo/options/clientoptions_test.go @@ -467,107 +467,14 @@ func TestClientOptions(t *testing.T) { OIDCMachineCallback: emptyCb, AuthMechanismProperties: map[string]string{"ALLOWED_HOSTS": "www.example.com"}, }), - err: fmt.Errorf("Cannot specify ALLOWED_HOSTS without an OIDCHumanCallback"), + err: fmt.Errorf("cannot specify ALLOWED_HOSTS without an OIDCHumanCallback"), }, { name: "cannot set OIDCMachineCallback in GCP Environment", opts: Client().SetAuth(Credential{ AuthMechanism: "MONGODB-OIDC", OIDCMachineCallback: emptyCb, - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "gcp"}, - }), - err: fmt.Errorf(`OIDCMachineCallback cannot be specified with the gcp "ENVIRONMENT"`), - }, - { - name: "cannot set OIDCMachineCallback in AZURE Environment", - opts: Client().SetAuth(Credential{ - AuthMechanism: "MONGODB-OIDC", - OIDCMachineCallback: emptyCb, - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "azure"}, - }), - err: fmt.Errorf(`OIDCMachineCallback cannot be specified with the azure "ENVIRONMENT"`), - }, - { - name: "TOKEN_RESOURCE must be set in GCP Environment", - opts: Client().SetAuth(Credential{ - AuthMechanism: "MONGODB-OIDC", - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "gcp"}, - }), - err: fmt.Errorf(`"TOKEN_RESOURCE" must be set for the gcp "ENVIRONMENT"`), - }, - { - name: "TOKEN_RESOURCE must be set in AZURE Environment", - opts: Client().SetAuth(Credential{ - AuthMechanism: "MONGODB-OIDC", - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "azure"}, - }), - err: fmt.Errorf(`"TOKEN_RESOURCE" must be set for the azure "ENVIRONMENT"`), - }, - { - name: "TOKEN_RESOURCE must not be set in TEST Environment", - opts: Client().SetAuth(Credential{ - AuthMechanism: "MONGODB-OIDC", - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "test", "TOKEN_RESOURCE": "stuff"}, - }), - err: fmt.Errorf(`"TOKEN_RESOURCE" must not be set for the test "ENVIRONMENT"`), - }, - { - name: "TOKEN_RESOURCE must not be set in any other Environment", - opts: Client().SetAuth(Credential{ - AuthMechanism: "MONGODB-OIDC", - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "random env!", "TOKEN_RESOURCE": "stuff"}, - }), - err: fmt.Errorf(`"TOKEN_RESOURCE" must not be set for the random env! "ENVIRONMENT"`), - }, - } - for _, tc := range testCases { - tc := tc // Capture range variable. - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - err := tc.opts.Validate() - assert.Equal(t, tc.err, err, "want error %v, got error %v", tc.err, err) - }) - } - }) - t.Run("OIDC auth configuration validation", func(t *testing.T) { - t.Parallel() - - emptyCb := func(_ context.Context, _ *OIDCArgs) (*OIDCCredential, error) { - return nil, nil - } - - testCases := []struct { - name string - opts *ClientOptions - err error - }{ - { - name: "password must not be set", - opts: Client().SetAuth(Credential{AuthMechanism: "MONGODB-OIDC", Password: "password"}), - err: fmt.Errorf("password must not be set for the MONGODB-OIDC auth mechanism"), - }, - { - name: "cannot set both OIDCMachineCallback and OIDCHumanCallback simultaneously", - opts: Client().SetAuth(Credential{AuthMechanism: "MONGODB-OIDC", - OIDCMachineCallback: emptyCb, OIDCHumanCallback: emptyCb}), - err: fmt.Errorf("cannot set both OIDCMachineCallback and OIDCHumanCallback, only one may be specified"), - }, - { - name: "cannot set ALLOWED_HOSTS without OIDCHumanCallback", - opts: Client().SetAuth(Credential{AuthMechanism: "MONGODB-OIDC", - OIDCMachineCallback: emptyCb, - AuthMechanismProperties: map[string]string{"ALLOWED_HOSTS": "www.example.com"}, - }), - err: fmt.Errorf("Cannot specify ALLOWED_HOSTS without an OIDCHumanCallback"), - }, - { - name: "cannot set OIDCMachineCallback in GCP Environment", - opts: Client().SetAuth(Credential{ - AuthMechanism: "MONGODB-OIDC", - OIDCMachineCallback: emptyCb, - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "gcp"}, + AuthMechanismProperties: map[string]string{"ENVIRONMENT": "gcp", "TOKEN_RESOURCE": "stuff"}, }), err: fmt.Errorf(`OIDCMachineCallback cannot be specified with the gcp "ENVIRONMENT"`), }, @@ -576,7 +483,7 @@ func TestClientOptions(t *testing.T) { opts: Client().SetAuth(Credential{ AuthMechanism: "MONGODB-OIDC", OIDCMachineCallback: emptyCb, - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "azure"}, + AuthMechanismProperties: map[string]string{"ENVIRONMENT": "azure", "TOKEN_RESOURCE": "stuff"}, }), err: fmt.Errorf(`OIDCMachineCallback cannot be specified with the azure "ENVIRONMENT"`), }, @@ -604,14 +511,6 @@ func TestClientOptions(t *testing.T) { }), err: fmt.Errorf(`"TOKEN_RESOURCE" must not be set for the test "ENVIRONMENT"`), }, - { - name: "TOKEN_RESOURCE must not be set in any other Environment", - opts: Client().SetAuth(Credential{ - AuthMechanism: "MONGODB-OIDC", - AuthMechanismProperties: map[string]string{"ENVIRONMENT": "random env!", "TOKEN_RESOURCE": "stuff"}, - }), - err: fmt.Errorf(`"TOKEN_RESOURCE" must not be set for the random env! "ENVIRONMENT"`), - }, } for _, tc := range testCases { tc := tc // Capture range variable. diff --git a/testdata/auth/connection-string.json b/testdata/auth/connection-string.json index 2a37ae8df4..3a099c8137 100644 --- a/testdata/auth/connection-string.json +++ b/testdata/auth/connection-string.json @@ -80,7 +80,7 @@ }, { "description": "should accept generic mechanism property (GSSAPI)", - "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com", "valid": true, "credential": { "username": "user@DOMAIN.COM", @@ -89,10 +89,46 @@ "mechanism": "GSSAPI", "mechanism_properties": { "SERVICE_NAME": "other", - "CANONICALIZE_HOST_NAME": true + "SERVICE_HOST": "example.com", + "CANONICALIZE_HOST_NAME": "forward" } } }, + { + "description": "should accept forwardAndReverse hostname canonicalization (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forwardAndReverse", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": null, + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "other", + "CANONICALIZE_HOST_NAME": "forwardAndReverse" + } + } + }, + { + "description": "should accept no hostname canonicalization (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:none", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": null, + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "other", + "CANONICALIZE_HOST_NAME": "none" + } + } + }, + { + "description": "must raise an error when the hostname canonicalization is invalid", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:invalid", + "valid": false + }, { "description": "should accept the password (GSSAPI)", "uri": "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external", @@ -127,47 +163,6 @@ "uri": "mongodb://localhost/?authMechanism=GSSAPI", "valid": false }, - { - "description": "should recognize the mechanism (MONGODB-CR)", - "uri": "mongodb://user:password@localhost/?authMechanism=MONGODB-CR", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "admin", - "mechanism": "MONGODB-CR", - "mechanism_properties": null - } - }, - { - "description": "should use the database when no authSource is specified (MONGODB-CR)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "foo", - "mechanism": "MONGODB-CR", - "mechanism_properties": null - } - }, - { - "description": "should use the authSource when specified (MONGODB-CR)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR&authSource=bar", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "bar", - "mechanism": "MONGODB-CR", - "mechanism_properties": null - } - }, - { - "description": "should throw an exception if no username is supplied (MONGODB-CR)", - "uri": "mongodb://localhost/?authMechanism=MONGODB-CR", - "valid": false - }, { "description": "should recognize the mechanism (MONGODB-X509)", "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509", @@ -444,6 +439,213 @@ "AWS_SESSION_TOKEN": "token!@#$%^&*()_+" } } + }, + { + "description": "should recognise the mechanism with test environment (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "test" + } + } + }, + { + "description": "should recognise the mechanism when auth source is explicitly specified and with environment (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authSource=$external&authMechanismProperties=ENVIRONMENT:test", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "test" + } + } + }, + { + "description": "should throw an exception if supplied a password (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception if username is specified for test (MONGODB-OIDC)", + "uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception if specified environment is not supported (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:invalid", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception if neither environment nor callbacks specified (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception when unsupported auth property is specified (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=UnsupportedProperty:unexisted", + "valid": false, + "credential": null + }, + { + "description": "should recognise the mechanism with azure provider (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "foo" + } + } + }, + { + "description": "should accept a username with azure provider (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "foo" + } + } + }, + { + "description": "should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "mongodb://test-cluster" + } + } + }, + { + "description": "should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "mongodb://test-cluster" + } + } + }, + { + "description": "should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abcd%25ef%3Ag%26hi", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "abcd%ef:g&hi" + } + } + }, + { + "description": "should url-encode a TOKEN_RESOURCE (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "a$b" + } + } + }, + { + "description": "should accept a username and throw an error for a password with azure provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception if no token audience is given for azure provider (MONGODB-OIDC)", + "uri": "mongodb://username@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure", + "valid": false, + "credential": null + }, + { + "description": "should recognise the mechanism with gcp provider (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:foo", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "gcp", + "TOKEN_RESOURCE": "foo" + } + } + }, + { + "description": "should throw an error for a username and password with gcp provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:foo", + "valid": false, + "credential": null + }, + { + "description": "should throw an error if not TOKEN_RESOURCE with gcp provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp", + "valid": false, + "credential": null + }, + { + "description": "should recognise the mechanism with k8s provider (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "k8s" + } + } + }, + { + "description": "should throw an error for a username and password with k8s provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s", + "valid": false, + "credential": null } ] } diff --git a/testdata/auth/connection-string.yml b/testdata/auth/connection-string.yml index 41dca8fabd..1f5d470041 100644 --- a/testdata/auth/connection-string.yml +++ b/testdata/auth/connection-string.yml @@ -1,366 +1,471 @@ +--- tests: - - - description: "should use the default source and mechanism" - uri: "mongodb://user:password@localhost" - valid: true - credential: - username: "user" - password: "password" - source: "admin" - mechanism: ~ - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified" - uri: "mongodb://user:password@localhost/foo" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: ~ - mechanism_properties: ~ - - - description: "should use the authSource when specified" - uri: "mongodb://user:password@localhost/foo?authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: ~ - mechanism_properties: ~ - - - description: "should recognise the mechanism (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI" - valid: true - credential: - username: "user@DOMAIN.COM" - password: ~ - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "mongodb" - - - description: "should ignore the database (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/foo?authMechanism=GSSAPI" - valid: true - credential: - username: "user@DOMAIN.COM" - password: ~ - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "mongodb" - - - description: "should accept valid authSource (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=$external" - valid: true - credential: - username: "user@DOMAIN.COM" - password: ~ - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "mongodb" - - - description: "should accept generic mechanism property (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true" - valid: true - credential: - username: "user@DOMAIN.COM" - password: ~ - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "other" - CANONICALIZE_HOST_NAME: true - - - description: "should accept the password (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external" - valid: true - credential: - username: "user@DOMAIN.COM" - password: "password" - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "mongodb" - - - description: "must raise an error when the authSource is empty" - uri: "mongodb://user:password@localhost/foo?authSource=" - valid: false - - - description: "must raise an error when the authSource is empty without credentials" - uri: "mongodb://localhost/admin?authSource=" - valid: false - - - description: "should throw an exception if authSource is invalid (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=foo" - valid: false - - - description: "should throw an exception if no username (GSSAPI)" - uri: "mongodb://localhost/?authMechanism=GSSAPI" - valid: false - - - description: "should recognize the mechanism (MONGODB-CR)" - uri: "mongodb://user:password@localhost/?authMechanism=MONGODB-CR" - valid: true - credential: - username: "user" - password: "password" - source: "admin" - mechanism: "MONGODB-CR" - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified (MONGODB-CR)" - uri: "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: "MONGODB-CR" - mechanism_properties: ~ - - - description: "should use the authSource when specified (MONGODB-CR)" - uri: "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR&authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: "MONGODB-CR" - mechanism_properties: ~ - - - description: "should throw an exception if no username is supplied (MONGODB-CR)" - uri: "mongodb://localhost/?authMechanism=MONGODB-CR" - valid: false - - - description: "should recognize the mechanism (MONGODB-X509)" - uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509" - valid: true - credential: - username: "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry" - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should ignore the database (MONGODB-X509)" - uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509" - valid: true - credential: - username: "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry" - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should accept valid authSource (MONGODB-X509)" - uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509&authSource=$external" - valid: true - credential: - username: "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry" - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should recognize the mechanism with no username (MONGODB-X509)" - uri: "mongodb://localhost/?authMechanism=MONGODB-X509" - valid: true - credential: - username: ~ - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should recognize the mechanism with no username when auth source is explicitly specified (MONGODB-X509)" - uri: "mongodb://localhost/?authMechanism=MONGODB-X509&authSource=$external" - valid: true - credential: - username: ~ - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should throw an exception if supplied a password (MONGODB-X509)" - uri: "mongodb://user:password@localhost/?authMechanism=MONGODB-X509" - valid: false - - - description: "should throw an exception if authSource is invalid (MONGODB-X509)" - uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509&authSource=bar" - valid: false - - - description: "should recognize the mechanism (PLAIN)" - uri: "mongodb://user:password@localhost/?authMechanism=PLAIN" - valid: true - credential: - username: "user" - password: "password" - source: "$external" - mechanism: "PLAIN" - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified (PLAIN)" - uri: "mongodb://user:password@localhost/foo?authMechanism=PLAIN" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: "PLAIN" - mechanism_properties: ~ - - - description: "should use the authSource when specified (PLAIN)" - uri: "mongodb://user:password@localhost/foo?authMechanism=PLAIN&authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: "PLAIN" - mechanism_properties: ~ - - - description: "should throw an exception if no username (PLAIN)" - uri: "mongodb://localhost/?authMechanism=PLAIN" - valid: false - - - description: "should recognize the mechanism (SCRAM-SHA-1)" - uri: "mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-1" - valid: true - credential: - username: "user" - password: "password" - source: "admin" - mechanism: "SCRAM-SHA-1" - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified (SCRAM-SHA-1)" - uri: "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: "SCRAM-SHA-1" - mechanism_properties: ~ - - - description: "should accept valid authSource (SCRAM-SHA-1)" - uri: "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1&authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: "SCRAM-SHA-1" - mechanism_properties: ~ - - - description: "should throw an exception if no username (SCRAM-SHA-1)" - uri: "mongodb://localhost/?authMechanism=SCRAM-SHA-1" - valid: false - - - description: "should recognize the mechanism (SCRAM-SHA-256)" - uri: "mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-256" - valid: true - credential: - username: "user" - password: "password" - source: "admin" - mechanism: "SCRAM-SHA-256" - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified (SCRAM-SHA-256)" - uri: "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: "SCRAM-SHA-256" - mechanism_properties: ~ - - - description: "should accept valid authSource (SCRAM-SHA-256)" - uri: "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256&authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: "SCRAM-SHA-256" - mechanism_properties: ~ - - - description: "should throw an exception if no username (SCRAM-SHA-256)" - uri: "mongodb://localhost/?authMechanism=SCRAM-SHA-256" - valid: false - - - description: "URI with no auth-related info doesn't create credential" - uri: "mongodb://localhost/" - valid: true - credential: ~ - - - description: "database in URI path doesn't create credentials" - uri: "mongodb://localhost/foo" - valid: true - credential: ~ - - - description: "authSource without username doesn't create credential (default mechanism)" - uri: "mongodb://localhost/?authSource=foo" - valid: true - credential: ~ - - - description: "should throw an exception if no username provided (userinfo implies default mechanism)" - uri: "mongodb://@localhost.com/" - valid: false - - - description: "should throw an exception if no username/password provided (userinfo implies default mechanism)" - uri: "mongodb://:@localhost.com/" - valid: false - - - description: "should recognise the mechanism (MONGODB-AWS)" - uri: "mongodb://localhost/?authMechanism=MONGODB-AWS" - valid: true - credential: - username: ~ - password: ~ - source: "$external" - mechanism: "MONGODB-AWS" - mechanism_properties: ~ - - - description: "should recognise the mechanism when auth source is explicitly specified (MONGODB-AWS)" - uri: "mongodb://localhost/?authMechanism=MONGODB-AWS&authSource=$external" - valid: true - credential: - username: ~ - password: ~ - source: "$external" - mechanism: "MONGODB-AWS" - mechanism_properties: ~ - - - description: "should throw an exception if username and no password (MONGODB-AWS)" - uri: "mongodb://user@localhost/?authMechanism=MONGODB-AWS" - valid: false - credential: ~ - - - description: "should use username and password if specified (MONGODB-AWS)" - uri: "mongodb://user%21%40%23%24%25%5E%26%2A%28%29_%2B:pass%21%40%23%24%25%5E%26%2A%28%29_%2B@localhost/?authMechanism=MONGODB-AWS" - valid: true - credential: - username: "user!@#$%^&*()_+" - password: "pass!@#$%^&*()_+" - source: "$external" - mechanism: "MONGODB-AWS" - mechanism_properties: ~ - - - description: "should use username, password and session token if specified (MONGODB-AWS)" - uri: "mongodb://user:password@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token%21%40%23%24%25%5E%26%2A%28%29_%2B" - valid: true - credential: - username: "user" - password: "password" - source: "$external" - mechanism: "MONGODB-AWS" - mechanism_properties: - AWS_SESSION_TOKEN: "token!@#$%^&*()_+" +- description: should use the default source and mechanism + uri: mongodb://user:password@localhost + valid: true + credential: + username: user + password: password + source: admin + mechanism: + mechanism_properties: +- description: should use the database when no authSource is specified + uri: mongodb://user:password@localhost/foo + valid: true + credential: + username: user + password: password + source: foo + mechanism: + mechanism_properties: +- description: should use the authSource when specified + uri: mongodb://user:password@localhost/foo?authSource=bar + valid: true + credential: + username: user + password: password + source: bar + mechanism: + mechanism_properties: +- description: should recognise the mechanism (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: mongodb +- description: should ignore the database (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/foo?authMechanism=GSSAPI + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: mongodb +- description: should accept valid authSource (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=$external + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: mongodb +- description: should accept generic mechanism property (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: other + SERVICE_HOST: example.com + CANONICALIZE_HOST_NAME: forward +- description: should accept forwardAndReverse hostname canonicalization (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forwardAndReverse + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: other + CANONICALIZE_HOST_NAME: forwardAndReverse +- description: should accept no hostname canonicalization (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:none + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: other + CANONICALIZE_HOST_NAME: none +- description: must raise an error when the hostname canonicalization is invalid + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:invalid + valid: false +- description: should accept the password (GSSAPI) + uri: mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external + valid: true + credential: + username: user@DOMAIN.COM + password: password + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: mongodb +- description: must raise an error when the authSource is empty + uri: mongodb://user:password@localhost/foo?authSource= + valid: false +- description: must raise an error when the authSource is empty without credentials + uri: mongodb://localhost/admin?authSource= + valid: false +- description: should throw an exception if authSource is invalid (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=foo + valid: false +- description: should throw an exception if no username (GSSAPI) + uri: mongodb://localhost/?authMechanism=GSSAPI + valid: false +- description: should recognize the mechanism (MONGODB-X509) + uri: mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509 + valid: true + credential: + username: CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should ignore the database (MONGODB-X509) + uri: mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509 + valid: true + credential: + username: CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should accept valid authSource (MONGODB-X509) + uri: mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509&authSource=$external + valid: true + credential: + username: CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should recognize the mechanism with no username (MONGODB-X509) + uri: mongodb://localhost/?authMechanism=MONGODB-X509 + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should recognize the mechanism with no username when auth source is + explicitly specified (MONGODB-X509) + uri: mongodb://localhost/?authMechanism=MONGODB-X509&authSource=$external + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should throw an exception if supplied a password (MONGODB-X509) + uri: mongodb://user:password@localhost/?authMechanism=MONGODB-X509 + valid: false +- description: should throw an exception if authSource is invalid (MONGODB-X509) + uri: mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509&authSource=bar + valid: false +- description: should recognize the mechanism (PLAIN) + uri: mongodb://user:password@localhost/?authMechanism=PLAIN + valid: true + credential: + username: user + password: password + source: "$external" + mechanism: PLAIN + mechanism_properties: +- description: should use the database when no authSource is specified (PLAIN) + uri: mongodb://user:password@localhost/foo?authMechanism=PLAIN + valid: true + credential: + username: user + password: password + source: foo + mechanism: PLAIN + mechanism_properties: +- description: should use the authSource when specified (PLAIN) + uri: mongodb://user:password@localhost/foo?authMechanism=PLAIN&authSource=bar + valid: true + credential: + username: user + password: password + source: bar + mechanism: PLAIN + mechanism_properties: +- description: should throw an exception if no username (PLAIN) + uri: mongodb://localhost/?authMechanism=PLAIN + valid: false +- description: should recognize the mechanism (SCRAM-SHA-1) + uri: mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-1 + valid: true + credential: + username: user + password: password + source: admin + mechanism: SCRAM-SHA-1 + mechanism_properties: +- description: should use the database when no authSource is specified (SCRAM-SHA-1) + uri: mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1 + valid: true + credential: + username: user + password: password + source: foo + mechanism: SCRAM-SHA-1 + mechanism_properties: +- description: should accept valid authSource (SCRAM-SHA-1) + uri: mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1&authSource=bar + valid: true + credential: + username: user + password: password + source: bar + mechanism: SCRAM-SHA-1 + mechanism_properties: +- description: should throw an exception if no username (SCRAM-SHA-1) + uri: mongodb://localhost/?authMechanism=SCRAM-SHA-1 + valid: false +- description: should recognize the mechanism (SCRAM-SHA-256) + uri: mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-256 + valid: true + credential: + username: user + password: password + source: admin + mechanism: SCRAM-SHA-256 + mechanism_properties: +- description: should use the database when no authSource is specified (SCRAM-SHA-256) + uri: mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256 + valid: true + credential: + username: user + password: password + source: foo + mechanism: SCRAM-SHA-256 + mechanism_properties: +- description: should accept valid authSource (SCRAM-SHA-256) + uri: mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256&authSource=bar + valid: true + credential: + username: user + password: password + source: bar + mechanism: SCRAM-SHA-256 + mechanism_properties: +- description: should throw an exception if no username (SCRAM-SHA-256) + uri: mongodb://localhost/?authMechanism=SCRAM-SHA-256 + valid: false +- description: URI with no auth-related info doesn't create credential + uri: mongodb://localhost/ + valid: true + credential: +- description: database in URI path doesn't create credentials + uri: mongodb://localhost/foo + valid: true + credential: +- description: authSource without username doesn't create credential (default mechanism) + uri: mongodb://localhost/?authSource=foo + valid: true + credential: +- description: should throw an exception if no username provided (userinfo implies + default mechanism) + uri: mongodb://@localhost.com/ + valid: false +- description: should throw an exception if no username/password provided (userinfo + implies default mechanism) + uri: mongodb://:@localhost.com/ + valid: false +- description: should recognise the mechanism (MONGODB-AWS) + uri: mongodb://localhost/?authMechanism=MONGODB-AWS + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-AWS + mechanism_properties: +- description: should recognise the mechanism when auth source is explicitly specified + (MONGODB-AWS) + uri: mongodb://localhost/?authMechanism=MONGODB-AWS&authSource=$external + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-AWS + mechanism_properties: +- description: should throw an exception if username and no password (MONGODB-AWS) + uri: mongodb://user@localhost/?authMechanism=MONGODB-AWS + valid: false + credential: +- description: should use username and password if specified (MONGODB-AWS) + uri: mongodb://user%21%40%23%24%25%5E%26%2A%28%29_%2B:pass%21%40%23%24%25%5E%26%2A%28%29_%2B@localhost/?authMechanism=MONGODB-AWS + valid: true + credential: + username: user!@#$%^&*()_+ + password: pass!@#$%^&*()_+ + source: "$external" + mechanism: MONGODB-AWS + mechanism_properties: +- description: should use username, password and session token if specified (MONGODB-AWS) + uri: mongodb://user:password@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token%21%40%23%24%25%5E%26%2A%28%29_%2B + valid: true + credential: + username: user + password: password + source: "$external" + mechanism: MONGODB-AWS + mechanism_properties: + AWS_SESSION_TOKEN: token!@#$%^&*()_+ +- description: should recognise the mechanism with test environment (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: test +- description: should recognise the mechanism when auth source is explicitly specified and with environment (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authSource=$external&authMechanismProperties=ENVIRONMENT:test + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: test +- description: should throw an exception if supplied a password (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test + valid: false + credential: +- description: should throw an exception if username is specified for test (MONGODB-OIDC) + uri: mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test + valid: false + credential: +- description: should throw an exception if specified environment is not supported (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:invalid + valid: false + credential: +- description: should throw an exception if neither environment nor callbacks specified (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC + valid: false + credential: +- description: should throw an exception when unsupported auth property is specified (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=UnsupportedProperty:unexisted + valid: false + credential: +- description: should recognise the mechanism with azure provider (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo + valid: true + credential: + username: null + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: foo +- description: should accept a username with azure provider (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: foo +- description: should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: 'mongodb://test-cluster' +- description: should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: 'mongodb://test-cluster' +- description: should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abcd%25ef%3Ag%26hi + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: 'abcd%ef:g&hi' +- description: should url-encode a TOKEN_RESOURCE (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: a$b +- description: should accept a username and throw an error for a password with azure provider (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo + valid: false + credential: null +- description: should throw an exception if no token audience is given for azure provider (MONGODB-OIDC) + uri: mongodb://username@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure + valid: false + credential: null +- description: should recognise the mechanism with gcp provider (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:foo + valid: true + credential: + username: null + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: gcp + TOKEN_RESOURCE: foo +- description: should throw an error for a username and password with gcp provider + (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:foo + valid: false + credential: null +- description: should throw an error if not TOKEN_RESOURCE with gcp provider (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp + valid: false + credential: null +- description: should recognise the mechanism with k8s provider (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s + valid: true + credential: + username: null + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: k8s +- description: should throw an error for a username and password with k8s provider + (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s + valid: false + credential: null diff --git a/x/mongo/driver/auth/auth_spec_test.go b/x/mongo/driver/auth/auth_spec_test.go index 1c0701014d..e8d246ae34 100644 --- a/x/mongo/driver/auth/auth_spec_test.go +++ b/x/mongo/driver/auth/auth_spec_test.go @@ -18,6 +18,11 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var skippedTests = map[string]string{ + // GODRIVER-3175: Support will be added with GODRIVER-2129. + "must raise an error when the hostname canonicalization is invalid": "Support will be added with GODRIVER-2129.", +} + type credential struct { Username string Password *string @@ -48,16 +53,19 @@ func runTestsInFile(t *testing.T, dirname string, filename string) { var container testContainer require.NoError(t, json.Unmarshal(content, &container)) - // Remove ".json" from filename. - filename = filename[:len(filename)-5] - for _, testCase := range container.Tests { - runTest(t, filename, testCase) + t.Run(filename, func(t *testing.T) { + runTest(t, testCase) + }) } } -func runTest(t *testing.T, filename string, test testCase) { - t.Run(filename+":"+test.Description, func(t *testing.T) { +func runTest(t *testing.T, test testCase) { + if skipReason, ok := skippedTests[test.Description]; ok { + t.Skipf("skipping due to known failure: %q", skipReason) + } + + t.Run(test.Description, func(t *testing.T) { opts := options.Client().ApplyURI(test.URI) if test.Valid { diff --git a/x/mongo/driver/auth/oidc.go b/x/mongo/driver/auth/oidc.go index c476ac86c4..11d526210c 100644 --- a/x/mongo/driver/auth/oidc.go +++ b/x/mongo/driver/auth/oidc.go @@ -13,6 +13,7 @@ import ( "io" "net/http" "net/url" + "os" "regexp" "strings" "sync" @@ -27,33 +28,44 @@ import ( // MongoDBOIDC is the string constant for the MONGODB-OIDC authentication mechanism. const MongoDBOIDC = "MONGODB-OIDC" -// EnvironmentProp is the property key name that specifies the environment for the OIDC authenticator. -const EnvironmentProp = "ENVIRONMENT" +// Valid authMechanismProperties keys for MONGODB-OIDC. +const ( + // EnvironmentProp is the property key name that specifies the environment for the OIDC authenticator. + EnvironmentProp = "ENVIRONMENT" -// ResourceProp is the property key name that specifies the token resource for GCP and AZURE OIDC auth. -const ResourceProp = "TOKEN_RESOURCE" + // ResourceProp is the property key name that specifies the token resource for GCP and AZURE OIDC auth. + ResourceProp = "TOKEN_RESOURCE" -// AllowedHostsProp is the property key name that specifies the allowed hosts for the OIDC authenticator. -const AllowedHostsProp = "ALLOWED_HOSTS" + // AllowedHostsProp is the property key name that specifies the allowed hosts for the OIDC authenticator. + AllowedHostsProp = "ALLOWED_HOSTS" +) + +// Valid ENVIRONMENT authMechismProperty values for MONGODB-OIDC. +const ( + // AzureEnvironmentValue is the value for the Azure environment. + AzureEnvironmentValue = "azure" -// AzureEnvironmentValue is the value for the Azure environment. -const AzureEnvironmentValue = "azure" + // GCPEnvironmentValue is the value for the GCP environment. + GCPEnvironmentValue = "gcp" -// GCPEnvironmentValue is the value for the GCP environment. -const GCPEnvironmentValue = "gcp" + // K8SEnvironmentValue is the value for Kubernetes environments. + K8SEnvironmentValue = "k8s" -// TestEnvironmentValue is the value for the test environment. -const TestEnvironmentValue = "test" + // TestEnvironmentValue is the value for the test environment. + TestEnvironmentValue = "test" +) -const apiVersion = 1 -const invalidateSleepTimeout = 100 * time.Millisecond +const ( + apiVersion = 1 + invalidateSleepTimeout = 100 * time.Millisecond -// The CSOT specification says to apply a 1-minute timeout if "CSOT is not applied". That's -// ambiguous for the v1.x Go Driver because it could mean either "no timeout provided" or "CSOT not -// enabled". Always use a maximum timeout duration of 1 minute, allowing us to ignore the ambiguity. -// Contexts with a shorter timeout are unaffected. -const machineCallbackTimeout = time.Minute -const humanCallbackTimeout = 5 * time.Minute + // The CSOT specification says to apply a 1-minute timeout if "CSOT is not applied". That's + // ambiguous for the v1.x Go Driver because it could mean either "no timeout provided" or "CSOT not + // enabled". Always use a maximum timeout duration of 1 minute, allowing us to ignore the ambiguity. + // Contexts with a shorter timeout are unaffected. + machineCallbackTimeout = time.Minute + humanCallbackTimeout = 5 * time.Minute +) var defaultAllowedHosts = []*regexp.Regexp{ regexp.MustCompile(`^.*[.]mongodb[.]net(:\d+)?$`), @@ -119,14 +131,12 @@ func newOIDCAuthenticator(cred *Cred, httpClient *http.Client) (Authenticator, e if cred.Props != nil { if env, ok := cred.Props[EnvironmentProp]; ok { switch strings.ToLower(env) { - case AzureEnvironmentValue: - fallthrough - case GCPEnvironmentValue: + case AzureEnvironmentValue, GCPEnvironmentValue: if _, ok := cred.Props[ResourceProp]; !ok { return nil, fmt.Errorf("%q must be specified for %q %q", ResourceProp, env, EnvironmentProp) } fallthrough - case TestEnvironmentValue: + case K8SEnvironmentValue, TestEnvironmentValue: if cred.OIDCMachineCallback != nil || cred.OIDCHumanCallback != nil { return nil, fmt.Errorf("OIDC callbacks are not allowed for %q %q", env, EnvironmentProp) } @@ -281,6 +291,8 @@ func (oa *OIDCAuthenticator) providerCallback() (OIDCCallback, error) { return nil, newAuthError(fmt.Sprintf("%q must be specified for GCP OIDC", ResourceProp), nil) } return getGCPOIDCCallback(resource, oa.httpClient), nil + case K8SEnvironmentValue: + return k8sOIDCCallback, nil } return nil, fmt.Errorf("%q %q not supported for MONGODB-OIDC", EnvironmentProp, env) @@ -360,6 +372,29 @@ func getGCPOIDCCallback(resource string, httpClient *http.Client) OIDCCallback { } } +// k8sOIDCCallbackfunc is the callback for the Kubernetes token provider. +func k8sOIDCCallback(context.Context, *OIDCArgs) (*OIDCCredential, error) { + // Check for the presence of the Azure and AWS token file path environment + // variables. If neither are set, use the GKE default token file path. + var path string + if p := os.Getenv("AZURE_FEDERATED_TOKEN_FILE"); p != "" { + path = p + } else if p := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE"); p != "" { + path = p + } else { + path = "/var/run/secrets/kubernetes.io/serviceaccount/token" + } + + token, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("error reading OIDC token from %q: %w", path, err) + } + + return &OIDCCredential{ + AccessToken: string(token), + }, nil +} + func (oa *OIDCAuthenticator) getAccessToken( ctx context.Context, conn *mnet.Connection,