diff --git a/api/v1/mdb/mongodb_types.go b/api/v1/mdb/mongodb_types.go index a27058033..58ea251fe 100644 --- a/api/v1/mdb/mongodb_types.go +++ b/api/v1/mdb/mongodb_types.go @@ -57,6 +57,12 @@ const ( ClusterTopologySingleCluster = "SingleCluster" ClusterTopologyMultiCluster = "MultiCluster" + OIDCAuthorizationTypeGroupMembership = "GroupMembership" + OIDCAuthorizationTypeUserID = "UserID" + + OIDCAuthorizationMethodWorkforceIdentityFederation = "WorkforceIdentityFederation" + OIDCAuthorizationMethodWorkloadIdentityFederation = "WorkloadIdentityFederation" + LabelResourceOwner = "mongodb.com/v1.mongodbResourceOwner" ) @@ -801,6 +807,13 @@ func (s *Security) IsTLSEnabled() bool { return s.CertificatesSecretsPrefix != "" } +func (s *Security) IsOIDCEnabled() bool { + if s == nil || s.Authentication == nil || !s.Authentication.Enabled { + return false + } + return s.Authentication.IsOIDCEnabled() +} + // GetAgentMechanism returns the authentication mechanism that the agents will be using. // The agents will use X509 if it is the only mechanism specified, otherwise they will use SCRAM if specified // and no auth if no mechanisms exist. @@ -878,7 +891,7 @@ func (s Security) RequiresClientTLSAuthentication() bool { return false } - if len(s.Authentication.Modes) == 1 && IsAuthPresent(s.Authentication.Modes, util.X509) { + if len(s.Authentication.Modes) == 1 && s.Authentication.IsX509Enabled() { return true } @@ -912,6 +925,10 @@ type Authentication struct { // +optional Ldap *Ldap `json:"ldap,omitempty"` + // Configuration for OIDC providers + // +optional + OIDCProviderConfigs []OIDCProviderConfig `json:"oidcProviderConfigs,omitempty"` + // Agents contains authentication configuration properties for the agents // +optional Agents AgentAuthentication `json:"agents,omitempty"` @@ -920,7 +937,7 @@ type Authentication struct { RequiresClientTLSAuthentication bool `json:"requireClientTLSAuthentication,omitempty"` } -// +kubebuilder:validation:Enum=X509;SCRAM;SCRAM-SHA-1;MONGODB-CR;SCRAM-SHA-256;LDAP +// +kubebuilder:validation:Enum=X509;SCRAM;SCRAM-SHA-1;MONGODB-CR;SCRAM-SHA-256;LDAP;OIDC type AuthMode string func ConvertAuthModesToStrings(authModes []AuthMode) []string { @@ -993,10 +1010,15 @@ func (a *Authentication) IsX509Enabled() bool { } // IsLDAPEnabled determines if LDAP is to be enabled at the project level -func (a *Authentication) isLDAPEnabled() bool { +func (a *Authentication) IsLDAPEnabled() bool { return stringutil.Contains(a.GetModes(), util.LDAP) } +// IsOIDCEnabled determines if OIDC is to be enabled at the project level +func (a *Authentication) IsOIDCEnabled() bool { + return stringutil.Contains(a.GetModes(), util.OIDC) +} + // GetModes returns the modes of the Authentication instance of an empty // list if it is nil func (a *Authentication) GetModes() []string { @@ -1033,6 +1055,68 @@ type Ldap struct { UserCacheInvalidationInterval int `json:"userCacheInvalidationInterval"` } +type OIDCProviderConfig struct { + // Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + // creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + // - alphanumeric characters (combination of a to z and 0 to 9) + // - hyphens (-) + // - underscores (_) + // +kubebuilder:validation:Pattern="^[a-zA-Z0-9-_]+$" + // +kubebuilder:validation:Required + ConfigurationName string `json:"configurationName"` + + // Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + // Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + // +kubebuilder:validation:Required + IssuerURI string `json:"issuerURI"` + + // Entity that your external identity provider intends the token for. + // Enter the audience value from the app you registered with external Identity Provider. + // +kubebuilder:validation:Required + Audience string `json:"audience"` + + // Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + // an individual user authorization. + // +kubebuilder:validation:Required + AuthorizationType OIDCAuthorizationType `json:"authorizationType"` + + // The identifier of the claim that includes the user principal identity. + // Accept the default value unless your IdP uses a different claim. + // +kubebuilder:default=sub + // +kubebuilder:validation:Required + UserClaim string `json:"userClaim"` + + // The identifier of the claim that includes the principal's IdP user group membership information. + // Accept the default value unless your IdP uses a different claim, or you need a custom claim. + // Required when selected GroupMembership as the authorization type, ignored otherwise + // +kubebuilder:default=groups + // +kubebuilder:validation:Optional + GroupsClaim string `json:"groupsClaim,omitempty"` + + // Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + // For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + // Only one Workforce Identity Federation IdP can be configured per MongoDB resource + // +kubebuilder:validation:Required + AuthorizationMethod OIDCAuthorizationMethod `json:"authorizationMethod"` + + // Unique identifier for your registered application. Enter the clientId value from the app you + // registered with an external Identity Provider. + // Required when selected Workforce Identity Federation authorization method + // +kubebuilder:validation:Optional + ClientId string `json:"clientId,omitempty"` + + // Tokens that give users permission to request data from the authorization endpoint. + // Only used for Workforce Identity Federation authorization method + // +kubebuilder:validation:Optional + RequestedScopes []string `json:"requestedScopes,omitempty"` +} + +// +kubebuilder:validation:Enum=GroupMembership;UserID +type OIDCAuthorizationType string + +// +kubebuilder:validation:Enum=WorkforceIdentityFederation;WorkloadIdentityFederation +type OIDCAuthorizationMethod string + type SecretRef struct { // +kubebuilder:validation:Required Name string `json:"name"` @@ -1142,7 +1226,14 @@ func (m *MongoDB) IsLDAPEnabled() bool { if m.Spec.Security == nil || m.Spec.Security.Authentication == nil { return false } - return IsAuthPresent(m.Spec.Security.Authentication.Modes, util.LDAP) + return m.Spec.Security.Authentication.IsLDAPEnabled() +} + +func (m *MongoDB) IsOIDCEnabled() bool { + if m.Spec.Security == nil || m.Spec.Security.Authentication == nil { + return false + } + return m.Spec.Security.Authentication.IsOIDCEnabled() } func (m *MongoDB) UpdateStatus(phase status.Phase, statusOptions ...status.Option) { @@ -1203,6 +1294,10 @@ func (m *MongoDB) GetStatus(...status.Option) interface{} { return m.Status } +func (m *MongoDB) GetStatusWarnings() []status.Warning { + return m.Status.Warnings +} + func (m *MongoDB) GetCommonStatus(...status.Option) *status.Common { return &m.Status.Common } diff --git a/api/v1/mdb/mongodb_types_test.go b/api/v1/mdb/mongodb_types_test.go index b17bbc04e..8566d6f71 100644 --- a/api/v1/mdb/mongodb_types_test.go +++ b/api/v1/mdb/mongodb_types_test.go @@ -61,6 +61,75 @@ func TestGetAgentAuthentication(t *testing.T) { assert.Equal(t, util.X509, sec.GetAgentMechanism("SCRAM-SHA-256"), "transitioning from SCRAM -> X509 is allowed") } +func TestGetAuthenticationIsEnabledMethods(t *testing.T) { + tests := []struct { + name string + authentication *Authentication + expectedX509 bool + expectedLDAP bool + expectedOIDC bool + }{ + { + name: "Nil authentication", + authentication: nil, + expectedX509: false, + expectedLDAP: false, + expectedOIDC: false, + }, + { + name: "Empty authentication", + authentication: newAuthentication(), + expectedX509: false, + expectedLDAP: false, + expectedOIDC: false, + }, + { + name: "Authentication with x509 only", + authentication: &Authentication{ + Modes: []AuthMode{util.X509}, + }, + expectedX509: true, + expectedLDAP: false, + expectedOIDC: false, + }, + { + name: "Authentication with LDAP only", + authentication: &Authentication{ + Modes: []AuthMode{util.LDAP}, + }, + expectedX509: false, + expectedLDAP: true, + expectedOIDC: false, + }, + { + name: "Authentication with OIDC only", + authentication: &Authentication{ + Modes: []AuthMode{util.OIDC}, + }, + expectedX509: false, + expectedLDAP: false, + expectedOIDC: true, + }, + { + name: "Authentication with multiple modes", + authentication: &Authentication{ + Modes: []AuthMode{util.X509, util.LDAP, util.OIDC, util.SCRAM}, + }, + expectedX509: true, + expectedLDAP: true, + expectedOIDC: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + auth := test.authentication + assert.Equal(t, test.expectedX509, auth.IsX509Enabled()) + assert.Equal(t, test.expectedLDAP, auth.IsLDAPEnabled()) + assert.Equal(t, test.expectedOIDC, auth.IsOIDCEnabled()) + }) + } +} + func TestMinimumMajorVersion(t *testing.T) { mdbSpec := MongoDbSpec{ DbCommonSpec: DbCommonSpec{ diff --git a/api/v1/mdb/mongodb_validation.go b/api/v1/mdb/mongodb_validation.go index e0d16dfdd..a9f07e7b6 100644 --- a/api/v1/mdb/mongodb_validation.go +++ b/api/v1/mdb/mongodb_validation.go @@ -105,9 +105,158 @@ func scramSha1AuthValidation(d DbCommonSpec) v1.ValidationResult { return v1.ValidationSuccess() } +func oidcAuthValidators(db DbCommonSpec) []func(DbCommonSpec) v1.ValidationResult { + validators := make([]func(DbCommonSpec) v1.ValidationResult, 0) + if !db.Security.IsOIDCEnabled() { + return validators + } + + authentication := db.Security.Authentication + validators = append(validators, oidcAuthModeValidator(authentication)) + + providerConfigs := authentication.OIDCProviderConfigs + if len(providerConfigs) == 0 { + return validators + } + + validators = append(validators, + oidcProviderConfigsUniqueNameValidation(providerConfigs), + oidcProviderConfigsSingleWorkforceIdentityFederationValidation(providerConfigs), + ) + + for _, config := range providerConfigs { + validators = append(validators, + oidcProviderConfigIssuerURIValidator(config), + oidcProviderConfigClientIdValidator(config), + oidcProviderConfigRequestedScopesValidator(config), + oidcProviderConfigAuthorizationTypeValidator(config), + oidcAuthRequiresEnterprise, + ) + } + + return validators +} + +func oidcAuthModeValidator(authentication *Authentication) func(DbCommonSpec) v1.ValidationResult { + return func(spec DbCommonSpec) v1.ValidationResult { + // OIDC cannot be used for agent authentication so other auth mode has to enabled as well + if len(authentication.Modes) == 1 { + return v1.ValidationError("OIDC authentication cannot be used as the only authentication mechanism") + } + + oidcProviderConfigs := authentication.OIDCProviderConfigs + if len(oidcProviderConfigs) == 0 { + return v1.ValidationError("At least one OIDC provider config needs to be specified when OIDC authentication is enabled") + } + + return v1.ValidationSuccess() + } +} + +func oidcProviderConfigsUniqueNameValidation(configs []OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult { + return func(spec DbCommonSpec) v1.ValidationResult { + configNames := make(map[string]bool) + for _, config := range configs { + if _, ok := configNames[config.ConfigurationName]; ok { + return v1.ValidationError("OIDC provider config name %s is not unique", config.ConfigurationName) + } + + configNames[config.ConfigurationName] = true + } + + return v1.ValidationSuccess() + } +} + +func oidcProviderConfigsSingleWorkforceIdentityFederationValidation(configs []OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult { + return func(spec DbCommonSpec) v1.ValidationResult { + workforceIdentityFederationConfigs := make([]string, 0) + for _, config := range configs { + if config.AuthorizationMethod == OIDCAuthorizationMethodWorkforceIdentityFederation { + workforceIdentityFederationConfigs = append(workforceIdentityFederationConfigs, config.ConfigurationName) + } + } + + if len(workforceIdentityFederationConfigs) > 1 { + configsSeparatedString := strings.Join(workforceIdentityFederationConfigs, ", ") + return v1.ValidationError("Only one OIDC provider config can be configured with Workforce Identity Federation. "+ + "The following configs are configured with Workforce Identity Federation: %s", configsSeparatedString) + } + + return v1.ValidationSuccess() + } +} + +func oidcProviderConfigIssuerURIValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult { + return func(_ DbCommonSpec) v1.ValidationResult { + url, err := util.ParseURL(config.IssuerURI) + if err != nil { + return v1.ValidationError("Invalid IssuerURI in OIDC provider config %q: %s", config.ConfigurationName, err.Error()) + } + + if url.Scheme != "https" { + return v1.ValidationWarning("IssuerURI %s in OIDC provider config %q in not secure endpoint", url.String(), config.ConfigurationName) + } + + return v1.ValidationSuccess() + } +} + +func oidcProviderConfigClientIdValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult { + return func(_ DbCommonSpec) v1.ValidationResult { + if config.AuthorizationMethod == OIDCAuthorizationMethodWorkforceIdentityFederation { + if config.ClientId == "" { + return v1.ValidationError("ClientId has to be specified in OIDC provider config %q with Workforce Identity Federation", config.ConfigurationName) + } + } else if config.AuthorizationMethod == OIDCAuthorizationMethodWorkloadIdentityFederation { + if config.ClientId != "" { + return v1.ValidationWarning("ClientId will be ignored in OIDC provider config %q with Workload Identity Federation", config.ConfigurationName) + } + } + + return v1.ValidationSuccess() + } +} + +func oidcProviderConfigRequestedScopesValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult { + return func(_ DbCommonSpec) v1.ValidationResult { + if config.AuthorizationMethod == OIDCAuthorizationMethodWorkloadIdentityFederation { + if len(config.RequestedScopes) > 0 { + return v1.ValidationWarning("RequestedScopes will be ignored in OIDC provider config %q with Workload Identity Federation", config.ConfigurationName) + } + } + + return v1.ValidationSuccess() + } +} + +func oidcProviderConfigAuthorizationTypeValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult { + return func(_ DbCommonSpec) v1.ValidationResult { + if config.AuthorizationType == OIDCAuthorizationTypeGroupMembership { + if config.GroupsClaim == "" { + return v1.ValidationError("GroupsClaim has to be specified in OIDC provider config %q when using Group Membership authorization", config.ConfigurationName) + } + } else if config.AuthorizationType == OIDCAuthorizationTypeUserID { + if config.GroupsClaim != "" { + return v1.ValidationWarning("GroupsClaim will be ignored in OIDC provider config %q when using User ID authorization", config.ConfigurationName) + } + } + + return v1.ValidationSuccess() + } +} + +func oidcAuthRequiresEnterprise(d DbCommonSpec) v1.ValidationResult { + authSpec := d.Security.Authentication + if authSpec != nil && authSpec.IsOIDCEnabled() && !strings.HasSuffix(d.Version, "-ent") { + return v1.ValidationError("Cannot enable OIDC authentication with MongoDB Community Builds") + } + return v1.ValidationSuccess() +} + func ldapAuthRequiresEnterprise(d DbCommonSpec) v1.ValidationResult { authSpec := d.Security.Authentication - if authSpec != nil && authSpec.isLDAPEnabled() && !strings.HasSuffix(d.Version, "-ent") { + if authSpec != nil && authSpec.IsLDAPEnabled() && !strings.HasSuffix(d.Version, "-ent") { return v1.ValidationError("Cannot enable LDAP authentication with MongoDB Community Builds") } return v1.ValidationSuccess() @@ -187,8 +336,8 @@ func specWithExactlyOneSchema(d DbCommonSpec) v1.ValidationResult { return v1.ValidationSuccess() } -func CommonValidators() []func(d DbCommonSpec) v1.ValidationResult { - return []func(d DbCommonSpec) v1.ValidationResult{ +func CommonValidators(db DbCommonSpec) []func(d DbCommonSpec) v1.ValidationResult { + validators := []func(d DbCommonSpec) v1.ValidationResult{ replicaSetHorizonsRequireTLS, deploymentsMustHaveTLSInX509Env, deploymentsMustHaveAtLeastOneAuthModeIfAuthIsEnabled, @@ -201,6 +350,10 @@ func CommonValidators() []func(d DbCommonSpec) v1.ValidationResult { specWithExactlyOneSchema, featureCompatibilityVersionValidation, } + + validators = append(validators, oidcAuthValidators(db)...) + + return validators } func featureCompatibilityVersionValidation(d DbCommonSpec) v1.ValidationResult { @@ -245,7 +398,7 @@ func (m *MongoDB) RunValidations(old *MongoDB) []v1.ValidationResult { } } - for _, validator := range CommonValidators() { + for _, validator := range CommonValidators(m.Spec.DbCommonSpec) { res := validator(m.Spec.DbCommonSpec) if res.Level > 0 { validationResults = append(validationResults, res) diff --git a/api/v1/mdb/mongodb_validation_test.go b/api/v1/mdb/mongodb_validation_test.go index 21f2263d1..075edada6 100644 --- a/api/v1/mdb/mongodb_validation_test.go +++ b/api/v1/mdb/mongodb_validation_test.go @@ -8,6 +8,7 @@ import ( "k8s.io/utils/ptr" v1 "github.com/mongodb/mongodb-kubernetes/api/v1" + "github.com/mongodb/mongodb-kubernetes/api/v1/status" "github.com/mongodb/mongodb-kubernetes/pkg/util" ) @@ -200,3 +201,299 @@ func TestReplicasetFCV(t *testing.T) { }) } } + +func TestOIDCAuthValidation(t *testing.T) { + tests := []struct { + name string + auth *Authentication + expectedErrorMessage string + expectedWarning status.Warning + }{ + { + name: "Authentication disabled", + auth: &Authentication{ + Enabled: false, + }, + }, + { + name: "OIDC not enabled", + auth: &Authentication{ + Enabled: true, + Modes: []AuthMode{util.SCRAMSHA256}, + }, + }, + { + name: "OIDC cannot be only authentication mode enabled", + auth: &Authentication{ + Enabled: true, + Modes: []AuthMode{util.OIDC}, + }, + expectedErrorMessage: "OIDC authentication cannot be used as the only authentication mechanism", + }, + { + name: "Agent authentication mode not specified, but required", + auth: &Authentication{ + Enabled: true, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + }, + expectedErrorMessage: "spec.security.authentication.agents.mode must be specified if more than one entry is present in spec.security.authentication.modes", + }, + { + name: "OIDC enabled but without provider configs", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + }, + expectedErrorMessage: "At least one OIDC provider config needs to be specified when OIDC authentication is enabled", + }, + { + name: "Multiple non unique configuration names", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "provider", + IssuerURI: "https://example1.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation, + ClientId: "clientId1", + }, + { + ConfigurationName: "provider", + IssuerURI: "https://example2.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation, + ClientId: "clientId2", + }, + }, + }, + expectedErrorMessage: "OIDC provider config name provider is not unique", + }, + { + name: "Multiple Workforce Identity Federation configs", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider1", + IssuerURI: "https://example1.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation, + ClientId: "clientId1", + }, + { + ConfigurationName: "test-provider2", + IssuerURI: "https://example2.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation, + ClientId: "clientId2", + }, + }, + }, + expectedErrorMessage: "Only one OIDC provider config can be configured with Workforce Identity Federation. The following configs are configured with Workforce Identity Federation: test-provider1, test-provider2", + }, + { + name: "Multiple Workload Identity Federation configs", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider-workforce1", + IssuerURI: "https://example1.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation, + ClientId: "clientId1", + }, + { + ConfigurationName: "test-provider-workload2", + IssuerURI: "https://example2.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation, + }, + { + ConfigurationName: "test-provider-workload3", + IssuerURI: "https://example3.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation, + }, + }, + }, + }, + { + name: "Invalid issuer URI", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider", + IssuerURI: "invalid-uri", + }, + }, + }, + expectedErrorMessage: "Invalid IssuerURI in OIDC provider config \"test-provider\": missing URL scheme: invalid-uri", + }, + { + name: "Non-HTTPS issuer URI - warning", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider", + IssuerURI: "http://example.com", + }, + }, + }, + expectedWarning: "IssuerURI http://example.com in OIDC provider config \"test-provider\" in not secure endpoint", + }, + { + name: "Workforce Identity Federation without ClientId", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider", + IssuerURI: "https://example.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation, + }, + }, + }, + expectedErrorMessage: "ClientId has to be specified in OIDC provider config \"test-provider\" with Workforce Identity Federation", + }, + { + name: "Workload Identity Federation with ClientId - warning", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider", + IssuerURI: "https://example.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation, + ClientId: "clientId", + }, + }, + }, + expectedWarning: "ClientId will be ignored in OIDC provider config \"test-provider\" with Workload Identity Federation", + }, + { + name: "Workload Identity Federation with RequestedScopes - warning", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider", + IssuerURI: "https://example.com", + AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation, + RequestedScopes: []string{"openid", "email"}, + }, + }, + }, + expectedWarning: "RequestedScopes will be ignored in OIDC provider config \"test-provider\" with Workload Identity Federation", + }, + { + name: "Group Membership authorization without GroupsClaim", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider1", + IssuerURI: "https://example.com", + AuthorizationType: OIDCAuthorizationTypeGroupMembership, + GroupsClaim: "groups", + }, + { + ConfigurationName: "test-provider2", + IssuerURI: "https://example.com", + AuthorizationType: OIDCAuthorizationTypeGroupMembership, + }, + }, + }, + expectedErrorMessage: "GroupsClaim has to be specified in OIDC provider config \"test-provider2\" when using Group Membership authorization", + }, + { + name: "User ID authorization with GroupsClaim - warning", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.SCRAMSHA256}, + Modes: []AuthMode{util.OIDC, util.SCRAMSHA256}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider1", + IssuerURI: "https://example.com", + AuthorizationType: OIDCAuthorizationTypeUserID, + GroupsClaim: "groups", + UserClaim: "sub", + }, + { + ConfigurationName: "test-provider2", + IssuerURI: "https://example.com", + AuthorizationType: OIDCAuthorizationTypeUserID, + UserClaim: "sub", + }, + }, + }, + expectedWarning: "GroupsClaim will be ignored in OIDC provider config \"test-provider1\" when using User ID authorization", + }, + { + name: "Valid OIDC configuration", + auth: &Authentication{ + Enabled: true, + Agents: AgentAuthentication{Mode: util.MONGODBCR}, + Modes: []AuthMode{util.OIDC, util.MONGODBCR}, + OIDCProviderConfigs: []OIDCProviderConfig{ + { + ConfigurationName: "test-provider1", + IssuerURI: "https://example.com", + AuthorizationType: OIDCAuthorizationTypeGroupMembership, + GroupsClaim: "groups", + }, + { + ConfigurationName: "test-provider2", + IssuerURI: "https://example.com", + AuthorizationType: OIDCAuthorizationTypeGroupMembership, + GroupsClaim: "groups", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rs := NewReplicaSetBuilder(). + SetSecurityTLSEnabled(). + SetVersion("8.0.5-ent"). + Build() + + rs.Spec.CloudManagerConfig = &PrivateCloudConfig{ + ConfigMapRef: ConfigMapRef{Name: "cloud-manager"}, + } + rs.Spec.Security.Authentication = tt.auth + + err := rs.ProcessValidationsOnReconcile(nil) + + if tt.expectedErrorMessage != "" { + assert.NotNil(t, err) + assert.Equal(t, tt.expectedErrorMessage, err.Error()) + } else { + assert.Nil(t, err) + } + + if tt.expectedWarning != "" { + warnings := rs.GetStatusWarnings() + assert.Contains(t, warnings, tt.expectedWarning) + } + }) + } +} diff --git a/api/v1/mdbmulti/mongodb_multi_types.go b/api/v1/mdbmulti/mongodb_multi_types.go index 00dc0ed7f..fa88b6082 100644 --- a/api/v1/mdbmulti/mongodb_multi_types.go +++ b/api/v1/mdbmulti/mongodb_multi_types.go @@ -23,7 +23,6 @@ import ( "github.com/mongodb/mongodb-kubernetes/pkg/multicluster/failedcluster" "github.com/mongodb/mongodb-kubernetes/pkg/util" intp "github.com/mongodb/mongodb-kubernetes/pkg/util/int" - "github.com/mongodb/mongodb-kubernetes/pkg/util/stringutil" ) func init() { @@ -123,7 +122,14 @@ func (m *MongoDBMultiCluster) IsLDAPEnabled() bool { if m.Spec.Security == nil || m.Spec.Security.Authentication == nil { return false } - return stringutil.Contains(m.Spec.GetSecurityAuthenticationModes(), util.LDAP) + return m.Spec.Security.Authentication.IsLDAPEnabled() +} + +func (m *MongoDBMultiCluster) IsOIDCEnabled() bool { + if m.Spec.Security == nil || m.Spec.Security.Authentication == nil { + return false + } + return m.Spec.Security.Authentication.IsOIDCEnabled() } func (m *MongoDBMultiCluster) GetLDAP(password, caContents string) *ldap.Ldap { diff --git a/api/v1/mdbmulti/mongodbmulti_validation.go b/api/v1/mdbmulti/mongodbmulti_validation.go index be2e3cae9..ff33ed781 100644 --- a/api/v1/mdbmulti/mongodbmulti_validation.go +++ b/api/v1/mdbmulti/mongodbmulti_validation.go @@ -53,7 +53,7 @@ func (m *MongoDBMultiCluster) RunValidations(old *MongoDBMultiCluster) []v1.Vali var validationResults []v1.ValidationResult - for _, validator := range mdbv1.CommonValidators() { + for _, validator := range mdbv1.CommonValidators(m.Spec.DbCommonSpec) { res := validator(m.Spec.DbCommonSpec) if res.Level > 0 { validationResults = append(validationResults, res) diff --git a/config/crd/bases/mongodb.com_mongodb.yaml b/config/crd/bases/mongodb.com_mongodb.yaml index a5b0287c2..8b5ffc33e 100644 --- a/config/crd/bases/mongodb.com_mongodb.yaml +++ b/config/crd/bases/mongodb.com_mongodb.yaml @@ -1521,8 +1521,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean diff --git a/config/crd/bases/mongodb.com_mongodbmulticluster.yaml b/config/crd/bases/mongodb.com_mongodbmulticluster.yaml index 0e1d65480..523238eb6 100644 --- a/config/crd/bases/mongodb.com_mongodbmulticluster.yaml +++ b/config/crd/bases/mongodb.com_mongodbmulticluster.yaml @@ -781,8 +781,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean diff --git a/config/crd/bases/mongodb.com_opsmanagers.yaml b/config/crd/bases/mongodb.com_opsmanagers.yaml index f0ba850a9..2c33c0e49 100644 --- a/config/crd/bases/mongodb.com_opsmanagers.yaml +++ b/config/crd/bases/mongodb.com_opsmanagers.yaml @@ -843,8 +843,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean diff --git a/helm_chart/crds/mongodb.com_mongodb.yaml b/helm_chart/crds/mongodb.com_mongodb.yaml index a5b0287c2..8b5ffc33e 100644 --- a/helm_chart/crds/mongodb.com_mongodb.yaml +++ b/helm_chart/crds/mongodb.com_mongodb.yaml @@ -1521,8 +1521,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean diff --git a/helm_chart/crds/mongodb.com_mongodbmulticluster.yaml b/helm_chart/crds/mongodb.com_mongodbmulticluster.yaml index 0e1d65480..523238eb6 100644 --- a/helm_chart/crds/mongodb.com_mongodbmulticluster.yaml +++ b/helm_chart/crds/mongodb.com_mongodbmulticluster.yaml @@ -781,8 +781,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean diff --git a/helm_chart/crds/mongodb.com_opsmanagers.yaml b/helm_chart/crds/mongodb.com_opsmanagers.yaml index f0ba850a9..2c33c0e49 100644 --- a/helm_chart/crds/mongodb.com_opsmanagers.yaml +++ b/helm_chart/crds/mongodb.com_opsmanagers.yaml @@ -843,8 +843,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean diff --git a/pkg/util/constants.go b/pkg/util/constants.go index a359838c0..d44b86223 100644 --- a/pkg/util/constants.go +++ b/pkg/util/constants.go @@ -152,6 +152,7 @@ const ( MONGODBCR = "MONGODB-CR" SCRAMSHA256 = "SCRAM-SHA-256" LDAP = "LDAP" + OIDC = "OIDC" MinimumScramSha256MdbVersion = "4.0.0" // pprof variables diff --git a/pkg/util/util.go b/pkg/util/util.go index ef2a8a654..13d6b5ac0 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -6,6 +6,7 @@ import ( "encoding/gob" "encoding/hex" "fmt" + "net/url" "regexp" "strings" "time" @@ -184,3 +185,29 @@ func TransformToMap[T any, K comparable, V any](objs []T, f func(obj T, idx int) } return result } + +// ParseURL checks if the given string is a valid URL and returns the parsed URL if valid. +func ParseURL(str string) (*url.URL, error) { + if strings.TrimSpace(str) == "" { + return nil, fmt.Errorf("empty URL") + } + + u, err := url.Parse(str) + if err != nil { + return nil, fmt.Errorf("invalid URL: %w", err) + } + + if u.Scheme == "" { + return nil, fmt.Errorf("missing URL scheme: %s", str) + } + + if u.Scheme != "http" && u.Scheme != "https" { + return nil, fmt.Errorf("invalid URL scheme (http or https): %s", str) + } + + if u.Host == "" { + return nil, fmt.Errorf("missing URL host: %s", str) + } + + return u, nil +} diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index 00548660b..cd639d41c 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/mongodb/mongodb-kubernetes/pkg/util/identifiable" ) @@ -197,6 +198,69 @@ func TestTransformToMap(t *testing.T) { })) } +// TestIsURL tests the ParseURL function with various inputs. +// +//goland:noinspection HttpUrlsUsage +func TestIsURL(t *testing.T) { + tests := []struct { + name string + input string + expectedErrorString string + }{ + { + name: "valid http URL", + input: "http://example.com", + }, + { + name: "valid https URL with path", + input: "https://example.com/path", + }, + { + name: "valid URL with port", + input: "http://example.com:8080", + }, + { + name: "missing scheme", + input: "example.com", + expectedErrorString: "missing URL scheme: example.com", + }, + { + name: "missing host", + input: "http://", + expectedErrorString: "missing URL host: http://", + }, + { + name: "empty string", + input: "", + expectedErrorString: "empty URL", + }, + { + name: "invalid URL", + input: ":invalid-url", + expectedErrorString: "invalid URL: parse \":invalid-url\": missing protocol scheme", + }, + { + name: "file scheme", + input: "file://path/to/file", + expectedErrorString: "invalid URL scheme (http or https): file://path/to/file", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + u, err := ParseURL(tt.input) + if tt.expectedErrorString != "" { + require.Error(t, err) + assert.Equal(t, tt.expectedErrorString, err.Error()) + assert.Nil(t, u) + } else { + assert.NoError(t, err) + assert.NotNil(t, u) + } + }) + } +} + func pair(left, right identifiable.Identifiable) []identifiable.Identifiable { return []identifiable.Identifiable{left, right} } diff --git a/public/crds.yaml b/public/crds.yaml index 006a9dd9e..00adc4cbd 100644 --- a/public/crds.yaml +++ b/public/crds.yaml @@ -1521,8 +1521,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean @@ -4081,8 +4157,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean @@ -5652,8 +5804,84 @@ spec: - MONGODB-CR - SCRAM-SHA-256 - LDAP + - OIDC type: string type: array + oidcProviderConfigs: + description: Configuration for OIDC providers + items: + properties: + audience: + description: |- + Entity that your external identity provider intends the token for. + Enter the audience value from the app you registered with external Identity Provider. + type: string + authorizationMethod: + description: |- + Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation. + For programmatic, application access to Ops Manager deployments use Workload Identity Federation. + Only one Workforce Identity Federation IdP can be configured per MongoDB resource + enum: + - WorkforceIdentityFederation + - WorkloadIdentityFederation + type: string + authorizationType: + description: |- + Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant + an individual user authorization. + enum: + - GroupMembership + - UserID + type: string + clientId: + description: |- + Unique identifier for your registered application. Enter the clientId value from the app you + registered with an external Identity Provider. + Required when selected Workforce Identity Federation authorization method + type: string + configurationName: + description: |- + Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when + creating users and roles for authorization. It is case-sensitive and can only contain the following characters: + - alphanumeric characters (combination of a to z and 0 to 9) + - hyphens (-) + - underscores (_) + pattern: ^[a-zA-Z0-9-_]+$ + type: string + groupsClaim: + default: groups + description: |- + The identifier of the claim that includes the principal's IdP user group membership information. + Accept the default value unless your IdP uses a different claim, or you need a custom claim. + Required when selected GroupMembership as the authorization type, ignored otherwise + type: string + issuerURI: + description: |- + Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider + Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint. + type: string + requestedScopes: + description: |- + Tokens that give users permission to request data from the authorization endpoint. + Only used for Workforce Identity Federation authorization method + items: + type: string + type: array + userClaim: + default: sub + description: |- + The identifier of the claim that includes the user principal identity. + Accept the default value unless your IdP uses a different claim. + type: string + required: + - audience + - authorizationMethod + - authorizationType + - configurationName + - issuerURI + - userClaim + type: object + type: array requireClientTLSAuthentication: description: Clients should present valid TLS certificates type: boolean