Skip to content

Commit aa1ba2e

Browse files
MaciejKaraslucian-tosaanandsyncs
authoredMay 22, 2025··
CLOUDP-314901 OIDC CRD changes + validation (#50)
# Summary Adding new `OIDCProviderConfig` struct to `Authentication` struct and new AuthMode `OIDC`. ```go 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 ``` ⚠️ Because `Security.Authentication` struct is reused also in AppDBSpec it will be available there as well. It will be as usual overridden in https://github.com/mongodb/mongodb-kubernetes/blob/f0050b8942545701e8cb9e42d54d14f0cb58ee6a/api/v1/om/opsmanager_types.go#L622 I believe it is worth noting this behaviour, but not change it as part of this project. ## Proof of Work New Unit test that verify validation and another set of webhook tests are under way. ## Next steps - [ ] Add release notes - [ ] Add validation tests based on webhooks - [ ] Start discussion with docs team ## Checklist - [x] Have you linked a jira ticket and/or is the ticket in the title? - [ ] Have you checked whether your jira ticket required DOCSP changes? - [ ] Have you checked for release_note changes? --------- Co-authored-by: Lucian Tosa <49226451+lucian-tosa@users.noreply.github.com> Co-authored-by: Anand <13899132+anandsyncs@users.noreply.github.com> Co-authored-by: Lucian Tosa <lucian.tosa@mongodb.com>
1 parent 3b5eec1 commit aa1ba2e

16 files changed

+1407
-11
lines changed
 

‎api/v1/mdb/mongodb_types.go

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ const (
5757
ClusterTopologySingleCluster = "SingleCluster"
5858
ClusterTopologyMultiCluster = "MultiCluster"
5959

60+
OIDCAuthorizationTypeGroupMembership = "GroupMembership"
61+
OIDCAuthorizationTypeUserID = "UserID"
62+
63+
OIDCAuthorizationMethodWorkforceIdentityFederation = "WorkforceIdentityFederation"
64+
OIDCAuthorizationMethodWorkloadIdentityFederation = "WorkloadIdentityFederation"
65+
6066
LabelResourceOwner = "mongodb.com/v1.mongodbResourceOwner"
6167
)
6268

@@ -801,6 +807,13 @@ func (s *Security) IsTLSEnabled() bool {
801807
return s.CertificatesSecretsPrefix != ""
802808
}
803809

810+
func (s *Security) IsOIDCEnabled() bool {
811+
if s == nil || s.Authentication == nil || !s.Authentication.Enabled {
812+
return false
813+
}
814+
return s.Authentication.IsOIDCEnabled()
815+
}
816+
804817
// GetAgentMechanism returns the authentication mechanism that the agents will be using.
805818
// The agents will use X509 if it is the only mechanism specified, otherwise they will use SCRAM if specified
806819
// and no auth if no mechanisms exist.
@@ -878,7 +891,7 @@ func (s Security) RequiresClientTLSAuthentication() bool {
878891
return false
879892
}
880893

881-
if len(s.Authentication.Modes) == 1 && IsAuthPresent(s.Authentication.Modes, util.X509) {
894+
if len(s.Authentication.Modes) == 1 && s.Authentication.IsX509Enabled() {
882895
return true
883896
}
884897

@@ -912,6 +925,10 @@ type Authentication struct {
912925
// +optional
913926
Ldap *Ldap `json:"ldap,omitempty"`
914927

928+
// Configuration for OIDC providers
929+
// +optional
930+
OIDCProviderConfigs []OIDCProviderConfig `json:"oidcProviderConfigs,omitempty"`
931+
915932
// Agents contains authentication configuration properties for the agents
916933
// +optional
917934
Agents AgentAuthentication `json:"agents,omitempty"`
@@ -920,7 +937,7 @@ type Authentication struct {
920937
RequiresClientTLSAuthentication bool `json:"requireClientTLSAuthentication,omitempty"`
921938
}
922939

923-
// +kubebuilder:validation:Enum=X509;SCRAM;SCRAM-SHA-1;MONGODB-CR;SCRAM-SHA-256;LDAP
940+
// +kubebuilder:validation:Enum=X509;SCRAM;SCRAM-SHA-1;MONGODB-CR;SCRAM-SHA-256;LDAP;OIDC
924941
type AuthMode string
925942

926943
func ConvertAuthModesToStrings(authModes []AuthMode) []string {
@@ -993,10 +1010,15 @@ func (a *Authentication) IsX509Enabled() bool {
9931010
}
9941011

9951012
// IsLDAPEnabled determines if LDAP is to be enabled at the project level
996-
func (a *Authentication) isLDAPEnabled() bool {
1013+
func (a *Authentication) IsLDAPEnabled() bool {
9971014
return stringutil.Contains(a.GetModes(), util.LDAP)
9981015
}
9991016

1017+
// IsOIDCEnabled determines if OIDC is to be enabled at the project level
1018+
func (a *Authentication) IsOIDCEnabled() bool {
1019+
return stringutil.Contains(a.GetModes(), util.OIDC)
1020+
}
1021+
10001022
// GetModes returns the modes of the Authentication instance of an empty
10011023
// list if it is nil
10021024
func (a *Authentication) GetModes() []string {
@@ -1033,6 +1055,68 @@ type Ldap struct {
10331055
UserCacheInvalidationInterval int `json:"userCacheInvalidationInterval"`
10341056
}
10351057

1058+
type OIDCProviderConfig struct {
1059+
// Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when
1060+
// creating users and roles for authorization. It is case-sensitive and can only contain the following characters:
1061+
// - alphanumeric characters (combination of a to z and 0 to 9)
1062+
// - hyphens (-)
1063+
// - underscores (_)
1064+
// +kubebuilder:validation:Pattern="^[a-zA-Z0-9-_]+$"
1065+
// +kubebuilder:validation:Required
1066+
ConfigurationName string `json:"configurationName"`
1067+
1068+
// Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider
1069+
// Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint.
1070+
// +kubebuilder:validation:Required
1071+
IssuerURI string `json:"issuerURI"`
1072+
1073+
// Entity that your external identity provider intends the token for.
1074+
// Enter the audience value from the app you registered with external Identity Provider.
1075+
// +kubebuilder:validation:Required
1076+
Audience string `json:"audience"`
1077+
1078+
// Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant
1079+
// an individual user authorization.
1080+
// +kubebuilder:validation:Required
1081+
AuthorizationType OIDCAuthorizationType `json:"authorizationType"`
1082+
1083+
// The identifier of the claim that includes the user principal identity.
1084+
// Accept the default value unless your IdP uses a different claim.
1085+
// +kubebuilder:default=sub
1086+
// +kubebuilder:validation:Required
1087+
UserClaim string `json:"userClaim"`
1088+
1089+
// The identifier of the claim that includes the principal's IdP user group membership information.
1090+
// Accept the default value unless your IdP uses a different claim, or you need a custom claim.
1091+
// Required when selected GroupMembership as the authorization type, ignored otherwise
1092+
// +kubebuilder:default=groups
1093+
// +kubebuilder:validation:Optional
1094+
GroupsClaim string `json:"groupsClaim,omitempty"`
1095+
1096+
// Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation.
1097+
// For programmatic, application access to Ops Manager deployments use Workload Identity Federation.
1098+
// Only one Workforce Identity Federation IdP can be configured per MongoDB resource
1099+
// +kubebuilder:validation:Required
1100+
AuthorizationMethod OIDCAuthorizationMethod `json:"authorizationMethod"`
1101+
1102+
// Unique identifier for your registered application. Enter the clientId value from the app you
1103+
// registered with an external Identity Provider.
1104+
// Required when selected Workforce Identity Federation authorization method
1105+
// +kubebuilder:validation:Optional
1106+
ClientId string `json:"clientId,omitempty"`
1107+
1108+
// Tokens that give users permission to request data from the authorization endpoint.
1109+
// Only used for Workforce Identity Federation authorization method
1110+
// +kubebuilder:validation:Optional
1111+
RequestedScopes []string `json:"requestedScopes,omitempty"`
1112+
}
1113+
1114+
// +kubebuilder:validation:Enum=GroupMembership;UserID
1115+
type OIDCAuthorizationType string
1116+
1117+
// +kubebuilder:validation:Enum=WorkforceIdentityFederation;WorkloadIdentityFederation
1118+
type OIDCAuthorizationMethod string
1119+
10361120
type SecretRef struct {
10371121
// +kubebuilder:validation:Required
10381122
Name string `json:"name"`
@@ -1142,7 +1226,14 @@ func (m *MongoDB) IsLDAPEnabled() bool {
11421226
if m.Spec.Security == nil || m.Spec.Security.Authentication == nil {
11431227
return false
11441228
}
1145-
return IsAuthPresent(m.Spec.Security.Authentication.Modes, util.LDAP)
1229+
return m.Spec.Security.Authentication.IsLDAPEnabled()
1230+
}
1231+
1232+
func (m *MongoDB) IsOIDCEnabled() bool {
1233+
if m.Spec.Security == nil || m.Spec.Security.Authentication == nil {
1234+
return false
1235+
}
1236+
return m.Spec.Security.Authentication.IsOIDCEnabled()
11461237
}
11471238

11481239
func (m *MongoDB) UpdateStatus(phase status.Phase, statusOptions ...status.Option) {
@@ -1203,6 +1294,10 @@ func (m *MongoDB) GetStatus(...status.Option) interface{} {
12031294
return m.Status
12041295
}
12051296

1297+
func (m *MongoDB) GetStatusWarnings() []status.Warning {
1298+
return m.Status.Warnings
1299+
}
1300+
12061301
func (m *MongoDB) GetCommonStatus(...status.Option) *status.Common {
12071302
return &m.Status.Common
12081303
}

‎api/v1/mdb/mongodb_types_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,75 @@ func TestGetAgentAuthentication(t *testing.T) {
6161
assert.Equal(t, util.X509, sec.GetAgentMechanism("SCRAM-SHA-256"), "transitioning from SCRAM -> X509 is allowed")
6262
}
6363

64+
func TestGetAuthenticationIsEnabledMethods(t *testing.T) {
65+
tests := []struct {
66+
name string
67+
authentication *Authentication
68+
expectedX509 bool
69+
expectedLDAP bool
70+
expectedOIDC bool
71+
}{
72+
{
73+
name: "Nil authentication",
74+
authentication: nil,
75+
expectedX509: false,
76+
expectedLDAP: false,
77+
expectedOIDC: false,
78+
},
79+
{
80+
name: "Empty authentication",
81+
authentication: newAuthentication(),
82+
expectedX509: false,
83+
expectedLDAP: false,
84+
expectedOIDC: false,
85+
},
86+
{
87+
name: "Authentication with x509 only",
88+
authentication: &Authentication{
89+
Modes: []AuthMode{util.X509},
90+
},
91+
expectedX509: true,
92+
expectedLDAP: false,
93+
expectedOIDC: false,
94+
},
95+
{
96+
name: "Authentication with LDAP only",
97+
authentication: &Authentication{
98+
Modes: []AuthMode{util.LDAP},
99+
},
100+
expectedX509: false,
101+
expectedLDAP: true,
102+
expectedOIDC: false,
103+
},
104+
{
105+
name: "Authentication with OIDC only",
106+
authentication: &Authentication{
107+
Modes: []AuthMode{util.OIDC},
108+
},
109+
expectedX509: false,
110+
expectedLDAP: false,
111+
expectedOIDC: true,
112+
},
113+
{
114+
name: "Authentication with multiple modes",
115+
authentication: &Authentication{
116+
Modes: []AuthMode{util.X509, util.LDAP, util.OIDC, util.SCRAM},
117+
},
118+
expectedX509: true,
119+
expectedLDAP: true,
120+
expectedOIDC: true,
121+
},
122+
}
123+
for _, test := range tests {
124+
t.Run(test.name, func(t *testing.T) {
125+
auth := test.authentication
126+
assert.Equal(t, test.expectedX509, auth.IsX509Enabled())
127+
assert.Equal(t, test.expectedLDAP, auth.IsLDAPEnabled())
128+
assert.Equal(t, test.expectedOIDC, auth.IsOIDCEnabled())
129+
})
130+
}
131+
}
132+
64133
func TestMinimumMajorVersion(t *testing.T) {
65134
mdbSpec := MongoDbSpec{
66135
DbCommonSpec: DbCommonSpec{

‎api/v1/mdb/mongodb_validation.go

Lines changed: 157 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,158 @@ func scramSha1AuthValidation(d DbCommonSpec) v1.ValidationResult {
105105
return v1.ValidationSuccess()
106106
}
107107

108+
func oidcAuthValidators(db DbCommonSpec) []func(DbCommonSpec) v1.ValidationResult {
109+
validators := make([]func(DbCommonSpec) v1.ValidationResult, 0)
110+
if !db.Security.IsOIDCEnabled() {
111+
return validators
112+
}
113+
114+
authentication := db.Security.Authentication
115+
validators = append(validators, oidcAuthModeValidator(authentication))
116+
117+
providerConfigs := authentication.OIDCProviderConfigs
118+
if len(providerConfigs) == 0 {
119+
return validators
120+
}
121+
122+
validators = append(validators,
123+
oidcProviderConfigsUniqueNameValidation(providerConfigs),
124+
oidcProviderConfigsSingleWorkforceIdentityFederationValidation(providerConfigs),
125+
)
126+
127+
for _, config := range providerConfigs {
128+
validators = append(validators,
129+
oidcProviderConfigIssuerURIValidator(config),
130+
oidcProviderConfigClientIdValidator(config),
131+
oidcProviderConfigRequestedScopesValidator(config),
132+
oidcProviderConfigAuthorizationTypeValidator(config),
133+
oidcAuthRequiresEnterprise,
134+
)
135+
}
136+
137+
return validators
138+
}
139+
140+
func oidcAuthModeValidator(authentication *Authentication) func(DbCommonSpec) v1.ValidationResult {
141+
return func(spec DbCommonSpec) v1.ValidationResult {
142+
// OIDC cannot be used for agent authentication so other auth mode has to enabled as well
143+
if len(authentication.Modes) == 1 {
144+
return v1.ValidationError("OIDC authentication cannot be used as the only authentication mechanism")
145+
}
146+
147+
oidcProviderConfigs := authentication.OIDCProviderConfigs
148+
if len(oidcProviderConfigs) == 0 {
149+
return v1.ValidationError("At least one OIDC provider config needs to be specified when OIDC authentication is enabled")
150+
}
151+
152+
return v1.ValidationSuccess()
153+
}
154+
}
155+
156+
func oidcProviderConfigsUniqueNameValidation(configs []OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult {
157+
return func(spec DbCommonSpec) v1.ValidationResult {
158+
configNames := make(map[string]bool)
159+
for _, config := range configs {
160+
if _, ok := configNames[config.ConfigurationName]; ok {
161+
return v1.ValidationError("OIDC provider config name %s is not unique", config.ConfigurationName)
162+
}
163+
164+
configNames[config.ConfigurationName] = true
165+
}
166+
167+
return v1.ValidationSuccess()
168+
}
169+
}
170+
171+
func oidcProviderConfigsSingleWorkforceIdentityFederationValidation(configs []OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult {
172+
return func(spec DbCommonSpec) v1.ValidationResult {
173+
workforceIdentityFederationConfigs := make([]string, 0)
174+
for _, config := range configs {
175+
if config.AuthorizationMethod == OIDCAuthorizationMethodWorkforceIdentityFederation {
176+
workforceIdentityFederationConfigs = append(workforceIdentityFederationConfigs, config.ConfigurationName)
177+
}
178+
}
179+
180+
if len(workforceIdentityFederationConfigs) > 1 {
181+
configsSeparatedString := strings.Join(workforceIdentityFederationConfigs, ", ")
182+
return v1.ValidationError("Only one OIDC provider config can be configured with Workforce Identity Federation. "+
183+
"The following configs are configured with Workforce Identity Federation: %s", configsSeparatedString)
184+
}
185+
186+
return v1.ValidationSuccess()
187+
}
188+
}
189+
190+
func oidcProviderConfigIssuerURIValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult {
191+
return func(_ DbCommonSpec) v1.ValidationResult {
192+
url, err := util.ParseURL(config.IssuerURI)
193+
if err != nil {
194+
return v1.ValidationError("Invalid IssuerURI in OIDC provider config %q: %s", config.ConfigurationName, err.Error())
195+
}
196+
197+
if url.Scheme != "https" {
198+
return v1.ValidationWarning("IssuerURI %s in OIDC provider config %q in not secure endpoint", url.String(), config.ConfigurationName)
199+
}
200+
201+
return v1.ValidationSuccess()
202+
}
203+
}
204+
205+
func oidcProviderConfigClientIdValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult {
206+
return func(_ DbCommonSpec) v1.ValidationResult {
207+
if config.AuthorizationMethod == OIDCAuthorizationMethodWorkforceIdentityFederation {
208+
if config.ClientId == "" {
209+
return v1.ValidationError("ClientId has to be specified in OIDC provider config %q with Workforce Identity Federation", config.ConfigurationName)
210+
}
211+
} else if config.AuthorizationMethod == OIDCAuthorizationMethodWorkloadIdentityFederation {
212+
if config.ClientId != "" {
213+
return v1.ValidationWarning("ClientId will be ignored in OIDC provider config %q with Workload Identity Federation", config.ConfigurationName)
214+
}
215+
}
216+
217+
return v1.ValidationSuccess()
218+
}
219+
}
220+
221+
func oidcProviderConfigRequestedScopesValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult {
222+
return func(_ DbCommonSpec) v1.ValidationResult {
223+
if config.AuthorizationMethod == OIDCAuthorizationMethodWorkloadIdentityFederation {
224+
if len(config.RequestedScopes) > 0 {
225+
return v1.ValidationWarning("RequestedScopes will be ignored in OIDC provider config %q with Workload Identity Federation", config.ConfigurationName)
226+
}
227+
}
228+
229+
return v1.ValidationSuccess()
230+
}
231+
}
232+
233+
func oidcProviderConfigAuthorizationTypeValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult {
234+
return func(_ DbCommonSpec) v1.ValidationResult {
235+
if config.AuthorizationType == OIDCAuthorizationTypeGroupMembership {
236+
if config.GroupsClaim == "" {
237+
return v1.ValidationError("GroupsClaim has to be specified in OIDC provider config %q when using Group Membership authorization", config.ConfigurationName)
238+
}
239+
} else if config.AuthorizationType == OIDCAuthorizationTypeUserID {
240+
if config.GroupsClaim != "" {
241+
return v1.ValidationWarning("GroupsClaim will be ignored in OIDC provider config %q when using User ID authorization", config.ConfigurationName)
242+
}
243+
}
244+
245+
return v1.ValidationSuccess()
246+
}
247+
}
248+
249+
func oidcAuthRequiresEnterprise(d DbCommonSpec) v1.ValidationResult {
250+
authSpec := d.Security.Authentication
251+
if authSpec != nil && authSpec.IsOIDCEnabled() && !strings.HasSuffix(d.Version, "-ent") {
252+
return v1.ValidationError("Cannot enable OIDC authentication with MongoDB Community Builds")
253+
}
254+
return v1.ValidationSuccess()
255+
}
256+
108257
func ldapAuthRequiresEnterprise(d DbCommonSpec) v1.ValidationResult {
109258
authSpec := d.Security.Authentication
110-
if authSpec != nil && authSpec.isLDAPEnabled() && !strings.HasSuffix(d.Version, "-ent") {
259+
if authSpec != nil && authSpec.IsLDAPEnabled() && !strings.HasSuffix(d.Version, "-ent") {
111260
return v1.ValidationError("Cannot enable LDAP authentication with MongoDB Community Builds")
112261
}
113262
return v1.ValidationSuccess()
@@ -187,8 +336,8 @@ func specWithExactlyOneSchema(d DbCommonSpec) v1.ValidationResult {
187336
return v1.ValidationSuccess()
188337
}
189338

190-
func CommonValidators() []func(d DbCommonSpec) v1.ValidationResult {
191-
return []func(d DbCommonSpec) v1.ValidationResult{
339+
func CommonValidators(db DbCommonSpec) []func(d DbCommonSpec) v1.ValidationResult {
340+
validators := []func(d DbCommonSpec) v1.ValidationResult{
192341
replicaSetHorizonsRequireTLS,
193342
deploymentsMustHaveTLSInX509Env,
194343
deploymentsMustHaveAtLeastOneAuthModeIfAuthIsEnabled,
@@ -201,6 +350,10 @@ func CommonValidators() []func(d DbCommonSpec) v1.ValidationResult {
201350
specWithExactlyOneSchema,
202351
featureCompatibilityVersionValidation,
203352
}
353+
354+
validators = append(validators, oidcAuthValidators(db)...)
355+
356+
return validators
204357
}
205358

206359
func featureCompatibilityVersionValidation(d DbCommonSpec) v1.ValidationResult {
@@ -245,7 +398,7 @@ func (m *MongoDB) RunValidations(old *MongoDB) []v1.ValidationResult {
245398
}
246399
}
247400

248-
for _, validator := range CommonValidators() {
401+
for _, validator := range CommonValidators(m.Spec.DbCommonSpec) {
249402
res := validator(m.Spec.DbCommonSpec)
250403
if res.Level > 0 {
251404
validationResults = append(validationResults, res)

‎api/v1/mdb/mongodb_validation_test.go

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"k8s.io/utils/ptr"
99

1010
v1 "github.com/mongodb/mongodb-kubernetes/api/v1"
11+
"github.com/mongodb/mongodb-kubernetes/api/v1/status"
1112
"github.com/mongodb/mongodb-kubernetes/pkg/util"
1213
)
1314

@@ -200,3 +201,299 @@ func TestReplicasetFCV(t *testing.T) {
200201
})
201202
}
202203
}
204+
205+
func TestOIDCAuthValidation(t *testing.T) {
206+
tests := []struct {
207+
name string
208+
auth *Authentication
209+
expectedErrorMessage string
210+
expectedWarning status.Warning
211+
}{
212+
{
213+
name: "Authentication disabled",
214+
auth: &Authentication{
215+
Enabled: false,
216+
},
217+
},
218+
{
219+
name: "OIDC not enabled",
220+
auth: &Authentication{
221+
Enabled: true,
222+
Modes: []AuthMode{util.SCRAMSHA256},
223+
},
224+
},
225+
{
226+
name: "OIDC cannot be only authentication mode enabled",
227+
auth: &Authentication{
228+
Enabled: true,
229+
Modes: []AuthMode{util.OIDC},
230+
},
231+
expectedErrorMessage: "OIDC authentication cannot be used as the only authentication mechanism",
232+
},
233+
{
234+
name: "Agent authentication mode not specified, but required",
235+
auth: &Authentication{
236+
Enabled: true,
237+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
238+
},
239+
expectedErrorMessage: "spec.security.authentication.agents.mode must be specified if more than one entry is present in spec.security.authentication.modes",
240+
},
241+
{
242+
name: "OIDC enabled but without provider configs",
243+
auth: &Authentication{
244+
Enabled: true,
245+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
246+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
247+
},
248+
expectedErrorMessage: "At least one OIDC provider config needs to be specified when OIDC authentication is enabled",
249+
},
250+
{
251+
name: "Multiple non unique configuration names",
252+
auth: &Authentication{
253+
Enabled: true,
254+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
255+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
256+
OIDCProviderConfigs: []OIDCProviderConfig{
257+
{
258+
ConfigurationName: "provider",
259+
IssuerURI: "https://example1.com",
260+
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
261+
ClientId: "clientId1",
262+
},
263+
{
264+
ConfigurationName: "provider",
265+
IssuerURI: "https://example2.com",
266+
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
267+
ClientId: "clientId2",
268+
},
269+
},
270+
},
271+
expectedErrorMessage: "OIDC provider config name provider is not unique",
272+
},
273+
{
274+
name: "Multiple Workforce Identity Federation configs",
275+
auth: &Authentication{
276+
Enabled: true,
277+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
278+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
279+
OIDCProviderConfigs: []OIDCProviderConfig{
280+
{
281+
ConfigurationName: "test-provider1",
282+
IssuerURI: "https://example1.com",
283+
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
284+
ClientId: "clientId1",
285+
},
286+
{
287+
ConfigurationName: "test-provider2",
288+
IssuerURI: "https://example2.com",
289+
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
290+
ClientId: "clientId2",
291+
},
292+
},
293+
},
294+
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",
295+
},
296+
{
297+
name: "Multiple Workload Identity Federation configs",
298+
auth: &Authentication{
299+
Enabled: true,
300+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
301+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
302+
OIDCProviderConfigs: []OIDCProviderConfig{
303+
{
304+
ConfigurationName: "test-provider-workforce1",
305+
IssuerURI: "https://example1.com",
306+
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
307+
ClientId: "clientId1",
308+
},
309+
{
310+
ConfigurationName: "test-provider-workload2",
311+
IssuerURI: "https://example2.com",
312+
AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation,
313+
},
314+
{
315+
ConfigurationName: "test-provider-workload3",
316+
IssuerURI: "https://example3.com",
317+
AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation,
318+
},
319+
},
320+
},
321+
},
322+
{
323+
name: "Invalid issuer URI",
324+
auth: &Authentication{
325+
Enabled: true,
326+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
327+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
328+
OIDCProviderConfigs: []OIDCProviderConfig{
329+
{
330+
ConfigurationName: "test-provider",
331+
IssuerURI: "invalid-uri",
332+
},
333+
},
334+
},
335+
expectedErrorMessage: "Invalid IssuerURI in OIDC provider config \"test-provider\": missing URL scheme: invalid-uri",
336+
},
337+
{
338+
name: "Non-HTTPS issuer URI - warning",
339+
auth: &Authentication{
340+
Enabled: true,
341+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
342+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
343+
OIDCProviderConfigs: []OIDCProviderConfig{
344+
{
345+
ConfigurationName: "test-provider",
346+
IssuerURI: "http://example.com",
347+
},
348+
},
349+
},
350+
expectedWarning: "IssuerURI http://example.com in OIDC provider config \"test-provider\" in not secure endpoint",
351+
},
352+
{
353+
name: "Workforce Identity Federation without ClientId",
354+
auth: &Authentication{
355+
Enabled: true,
356+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
357+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
358+
OIDCProviderConfigs: []OIDCProviderConfig{
359+
{
360+
ConfigurationName: "test-provider",
361+
IssuerURI: "https://example.com",
362+
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
363+
},
364+
},
365+
},
366+
expectedErrorMessage: "ClientId has to be specified in OIDC provider config \"test-provider\" with Workforce Identity Federation",
367+
},
368+
{
369+
name: "Workload Identity Federation with ClientId - warning",
370+
auth: &Authentication{
371+
Enabled: true,
372+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
373+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
374+
OIDCProviderConfigs: []OIDCProviderConfig{
375+
{
376+
ConfigurationName: "test-provider",
377+
IssuerURI: "https://example.com",
378+
AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation,
379+
ClientId: "clientId",
380+
},
381+
},
382+
},
383+
expectedWarning: "ClientId will be ignored in OIDC provider config \"test-provider\" with Workload Identity Federation",
384+
},
385+
{
386+
name: "Workload Identity Federation with RequestedScopes - warning",
387+
auth: &Authentication{
388+
Enabled: true,
389+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
390+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
391+
OIDCProviderConfigs: []OIDCProviderConfig{
392+
{
393+
ConfigurationName: "test-provider",
394+
IssuerURI: "https://example.com",
395+
AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation,
396+
RequestedScopes: []string{"openid", "email"},
397+
},
398+
},
399+
},
400+
expectedWarning: "RequestedScopes will be ignored in OIDC provider config \"test-provider\" with Workload Identity Federation",
401+
},
402+
{
403+
name: "Group Membership authorization without GroupsClaim",
404+
auth: &Authentication{
405+
Enabled: true,
406+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
407+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
408+
OIDCProviderConfigs: []OIDCProviderConfig{
409+
{
410+
ConfigurationName: "test-provider1",
411+
IssuerURI: "https://example.com",
412+
AuthorizationType: OIDCAuthorizationTypeGroupMembership,
413+
GroupsClaim: "groups",
414+
},
415+
{
416+
ConfigurationName: "test-provider2",
417+
IssuerURI: "https://example.com",
418+
AuthorizationType: OIDCAuthorizationTypeGroupMembership,
419+
},
420+
},
421+
},
422+
expectedErrorMessage: "GroupsClaim has to be specified in OIDC provider config \"test-provider2\" when using Group Membership authorization",
423+
},
424+
{
425+
name: "User ID authorization with GroupsClaim - warning",
426+
auth: &Authentication{
427+
Enabled: true,
428+
Agents: AgentAuthentication{Mode: util.SCRAMSHA256},
429+
Modes: []AuthMode{util.OIDC, util.SCRAMSHA256},
430+
OIDCProviderConfigs: []OIDCProviderConfig{
431+
{
432+
ConfigurationName: "test-provider1",
433+
IssuerURI: "https://example.com",
434+
AuthorizationType: OIDCAuthorizationTypeUserID,
435+
GroupsClaim: "groups",
436+
UserClaim: "sub",
437+
},
438+
{
439+
ConfigurationName: "test-provider2",
440+
IssuerURI: "https://example.com",
441+
AuthorizationType: OIDCAuthorizationTypeUserID,
442+
UserClaim: "sub",
443+
},
444+
},
445+
},
446+
expectedWarning: "GroupsClaim will be ignored in OIDC provider config \"test-provider1\" when using User ID authorization",
447+
},
448+
{
449+
name: "Valid OIDC configuration",
450+
auth: &Authentication{
451+
Enabled: true,
452+
Agents: AgentAuthentication{Mode: util.MONGODBCR},
453+
Modes: []AuthMode{util.OIDC, util.MONGODBCR},
454+
OIDCProviderConfigs: []OIDCProviderConfig{
455+
{
456+
ConfigurationName: "test-provider1",
457+
IssuerURI: "https://example.com",
458+
AuthorizationType: OIDCAuthorizationTypeGroupMembership,
459+
GroupsClaim: "groups",
460+
},
461+
{
462+
ConfigurationName: "test-provider2",
463+
IssuerURI: "https://example.com",
464+
AuthorizationType: OIDCAuthorizationTypeGroupMembership,
465+
GroupsClaim: "groups",
466+
},
467+
},
468+
},
469+
},
470+
}
471+
472+
for _, tt := range tests {
473+
t.Run(tt.name, func(t *testing.T) {
474+
rs := NewReplicaSetBuilder().
475+
SetSecurityTLSEnabled().
476+
SetVersion("8.0.5-ent").
477+
Build()
478+
479+
rs.Spec.CloudManagerConfig = &PrivateCloudConfig{
480+
ConfigMapRef: ConfigMapRef{Name: "cloud-manager"},
481+
}
482+
rs.Spec.Security.Authentication = tt.auth
483+
484+
err := rs.ProcessValidationsOnReconcile(nil)
485+
486+
if tt.expectedErrorMessage != "" {
487+
assert.NotNil(t, err)
488+
assert.Equal(t, tt.expectedErrorMessage, err.Error())
489+
} else {
490+
assert.Nil(t, err)
491+
}
492+
493+
if tt.expectedWarning != "" {
494+
warnings := rs.GetStatusWarnings()
495+
assert.Contains(t, warnings, tt.expectedWarning)
496+
}
497+
})
498+
}
499+
}

‎api/v1/mdbmulti/mongodb_multi_types.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/mongodb/mongodb-kubernetes/pkg/multicluster/failedcluster"
2424
"github.com/mongodb/mongodb-kubernetes/pkg/util"
2525
intp "github.com/mongodb/mongodb-kubernetes/pkg/util/int"
26-
"github.com/mongodb/mongodb-kubernetes/pkg/util/stringutil"
2726
)
2827

2928
func init() {
@@ -123,7 +122,14 @@ func (m *MongoDBMultiCluster) IsLDAPEnabled() bool {
123122
if m.Spec.Security == nil || m.Spec.Security.Authentication == nil {
124123
return false
125124
}
126-
return stringutil.Contains(m.Spec.GetSecurityAuthenticationModes(), util.LDAP)
125+
return m.Spec.Security.Authentication.IsLDAPEnabled()
126+
}
127+
128+
func (m *MongoDBMultiCluster) IsOIDCEnabled() bool {
129+
if m.Spec.Security == nil || m.Spec.Security.Authentication == nil {
130+
return false
131+
}
132+
return m.Spec.Security.Authentication.IsOIDCEnabled()
127133
}
128134

129135
func (m *MongoDBMultiCluster) GetLDAP(password, caContents string) *ldap.Ldap {

‎api/v1/mdbmulti/mongodbmulti_validation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (m *MongoDBMultiCluster) RunValidations(old *MongoDBMultiCluster) []v1.Vali
5353

5454
var validationResults []v1.ValidationResult
5555

56-
for _, validator := range mdbv1.CommonValidators() {
56+
for _, validator := range mdbv1.CommonValidators(m.Spec.DbCommonSpec) {
5757
res := validator(m.Spec.DbCommonSpec)
5858
if res.Level > 0 {
5959
validationResults = append(validationResults, res)

‎config/crd/bases/mongodb.com_mongodb.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,8 +1521,84 @@ spec:
15211521
- MONGODB-CR
15221522
- SCRAM-SHA-256
15231523
- LDAP
1524+
- OIDC
15241525
type: string
15251526
type: array
1527+
oidcProviderConfigs:
1528+
description: Configuration for OIDC providers
1529+
items:
1530+
properties:
1531+
audience:
1532+
description: |-
1533+
Entity that your external identity provider intends the token for.
1534+
Enter the audience value from the app you registered with external Identity Provider.
1535+
type: string
1536+
authorizationMethod:
1537+
description: |-
1538+
Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation.
1539+
For programmatic, application access to Ops Manager deployments use Workload Identity Federation.
1540+
Only one Workforce Identity Federation IdP can be configured per MongoDB resource
1541+
enum:
1542+
- WorkforceIdentityFederation
1543+
- WorkloadIdentityFederation
1544+
type: string
1545+
authorizationType:
1546+
description: |-
1547+
Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant
1548+
an individual user authorization.
1549+
enum:
1550+
- GroupMembership
1551+
- UserID
1552+
type: string
1553+
clientId:
1554+
description: |-
1555+
Unique identifier for your registered application. Enter the clientId value from the app you
1556+
registered with an external Identity Provider.
1557+
Required when selected Workforce Identity Federation authorization method
1558+
type: string
1559+
configurationName:
1560+
description: |-
1561+
Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when
1562+
creating users and roles for authorization. It is case-sensitive and can only contain the following characters:
1563+
- alphanumeric characters (combination of a to z and 0 to 9)
1564+
- hyphens (-)
1565+
- underscores (_)
1566+
pattern: ^[a-zA-Z0-9-_]+$
1567+
type: string
1568+
groupsClaim:
1569+
default: groups
1570+
description: |-
1571+
The identifier of the claim that includes the principal's IdP user group membership information.
1572+
Accept the default value unless your IdP uses a different claim, or you need a custom claim.
1573+
Required when selected GroupMembership as the authorization type, ignored otherwise
1574+
type: string
1575+
issuerURI:
1576+
description: |-
1577+
Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider
1578+
Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint.
1579+
type: string
1580+
requestedScopes:
1581+
description: |-
1582+
Tokens that give users permission to request data from the authorization endpoint.
1583+
Only used for Workforce Identity Federation authorization method
1584+
items:
1585+
type: string
1586+
type: array
1587+
userClaim:
1588+
default: sub
1589+
description: |-
1590+
The identifier of the claim that includes the user principal identity.
1591+
Accept the default value unless your IdP uses a different claim.
1592+
type: string
1593+
required:
1594+
- audience
1595+
- authorizationMethod
1596+
- authorizationType
1597+
- configurationName
1598+
- issuerURI
1599+
- userClaim
1600+
type: object
1601+
type: array
15261602
requireClientTLSAuthentication:
15271603
description: Clients should present valid TLS certificates
15281604
type: boolean

‎config/crd/bases/mongodb.com_mongodbmulticluster.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,8 +781,84 @@ spec:
781781
- MONGODB-CR
782782
- SCRAM-SHA-256
783783
- LDAP
784+
- OIDC
784785
type: string
785786
type: array
787+
oidcProviderConfigs:
788+
description: Configuration for OIDC providers
789+
items:
790+
properties:
791+
audience:
792+
description: |-
793+
Entity that your external identity provider intends the token for.
794+
Enter the audience value from the app you registered with external Identity Provider.
795+
type: string
796+
authorizationMethod:
797+
description: |-
798+
Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation.
799+
For programmatic, application access to Ops Manager deployments use Workload Identity Federation.
800+
Only one Workforce Identity Federation IdP can be configured per MongoDB resource
801+
enum:
802+
- WorkforceIdentityFederation
803+
- WorkloadIdentityFederation
804+
type: string
805+
authorizationType:
806+
description: |-
807+
Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant
808+
an individual user authorization.
809+
enum:
810+
- GroupMembership
811+
- UserID
812+
type: string
813+
clientId:
814+
description: |-
815+
Unique identifier for your registered application. Enter the clientId value from the app you
816+
registered with an external Identity Provider.
817+
Required when selected Workforce Identity Federation authorization method
818+
type: string
819+
configurationName:
820+
description: |-
821+
Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when
822+
creating users and roles for authorization. It is case-sensitive and can only contain the following characters:
823+
- alphanumeric characters (combination of a to z and 0 to 9)
824+
- hyphens (-)
825+
- underscores (_)
826+
pattern: ^[a-zA-Z0-9-_]+$
827+
type: string
828+
groupsClaim:
829+
default: groups
830+
description: |-
831+
The identifier of the claim that includes the principal's IdP user group membership information.
832+
Accept the default value unless your IdP uses a different claim, or you need a custom claim.
833+
Required when selected GroupMembership as the authorization type, ignored otherwise
834+
type: string
835+
issuerURI:
836+
description: |-
837+
Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider
838+
Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint.
839+
type: string
840+
requestedScopes:
841+
description: |-
842+
Tokens that give users permission to request data from the authorization endpoint.
843+
Only used for Workforce Identity Federation authorization method
844+
items:
845+
type: string
846+
type: array
847+
userClaim:
848+
default: sub
849+
description: |-
850+
The identifier of the claim that includes the user principal identity.
851+
Accept the default value unless your IdP uses a different claim.
852+
type: string
853+
required:
854+
- audience
855+
- authorizationMethod
856+
- authorizationType
857+
- configurationName
858+
- issuerURI
859+
- userClaim
860+
type: object
861+
type: array
786862
requireClientTLSAuthentication:
787863
description: Clients should present valid TLS certificates
788864
type: boolean

‎config/crd/bases/mongodb.com_opsmanagers.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,8 +843,84 @@ spec:
843843
- MONGODB-CR
844844
- SCRAM-SHA-256
845845
- LDAP
846+
- OIDC
846847
type: string
847848
type: array
849+
oidcProviderConfigs:
850+
description: Configuration for OIDC providers
851+
items:
852+
properties:
853+
audience:
854+
description: |-
855+
Entity that your external identity provider intends the token for.
856+
Enter the audience value from the app you registered with external Identity Provider.
857+
type: string
858+
authorizationMethod:
859+
description: |-
860+
Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation.
861+
For programmatic, application access to Ops Manager deployments use Workload Identity Federation.
862+
Only one Workforce Identity Federation IdP can be configured per MongoDB resource
863+
enum:
864+
- WorkforceIdentityFederation
865+
- WorkloadIdentityFederation
866+
type: string
867+
authorizationType:
868+
description: |-
869+
Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant
870+
an individual user authorization.
871+
enum:
872+
- GroupMembership
873+
- UserID
874+
type: string
875+
clientId:
876+
description: |-
877+
Unique identifier for your registered application. Enter the clientId value from the app you
878+
registered with an external Identity Provider.
879+
Required when selected Workforce Identity Federation authorization method
880+
type: string
881+
configurationName:
882+
description: |-
883+
Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when
884+
creating users and roles for authorization. It is case-sensitive and can only contain the following characters:
885+
- alphanumeric characters (combination of a to z and 0 to 9)
886+
- hyphens (-)
887+
- underscores (_)
888+
pattern: ^[a-zA-Z0-9-_]+$
889+
type: string
890+
groupsClaim:
891+
default: groups
892+
description: |-
893+
The identifier of the claim that includes the principal's IdP user group membership information.
894+
Accept the default value unless your IdP uses a different claim, or you need a custom claim.
895+
Required when selected GroupMembership as the authorization type, ignored otherwise
896+
type: string
897+
issuerURI:
898+
description: |-
899+
Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider
900+
Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint.
901+
type: string
902+
requestedScopes:
903+
description: |-
904+
Tokens that give users permission to request data from the authorization endpoint.
905+
Only used for Workforce Identity Federation authorization method
906+
items:
907+
type: string
908+
type: array
909+
userClaim:
910+
default: sub
911+
description: |-
912+
The identifier of the claim that includes the user principal identity.
913+
Accept the default value unless your IdP uses a different claim.
914+
type: string
915+
required:
916+
- audience
917+
- authorizationMethod
918+
- authorizationType
919+
- configurationName
920+
- issuerURI
921+
- userClaim
922+
type: object
923+
type: array
848924
requireClientTLSAuthentication:
849925
description: Clients should present valid TLS certificates
850926
type: boolean

‎helm_chart/crds/mongodb.com_mongodb.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,8 +1521,84 @@ spec:
15211521
- MONGODB-CR
15221522
- SCRAM-SHA-256
15231523
- LDAP
1524+
- OIDC
15241525
type: string
15251526
type: array
1527+
oidcProviderConfigs:
1528+
description: Configuration for OIDC providers
1529+
items:
1530+
properties:
1531+
audience:
1532+
description: |-
1533+
Entity that your external identity provider intends the token for.
1534+
Enter the audience value from the app you registered with external Identity Provider.
1535+
type: string
1536+
authorizationMethod:
1537+
description: |-
1538+
Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation.
1539+
For programmatic, application access to Ops Manager deployments use Workload Identity Federation.
1540+
Only one Workforce Identity Federation IdP can be configured per MongoDB resource
1541+
enum:
1542+
- WorkforceIdentityFederation
1543+
- WorkloadIdentityFederation
1544+
type: string
1545+
authorizationType:
1546+
description: |-
1547+
Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant
1548+
an individual user authorization.
1549+
enum:
1550+
- GroupMembership
1551+
- UserID
1552+
type: string
1553+
clientId:
1554+
description: |-
1555+
Unique identifier for your registered application. Enter the clientId value from the app you
1556+
registered with an external Identity Provider.
1557+
Required when selected Workforce Identity Federation authorization method
1558+
type: string
1559+
configurationName:
1560+
description: |-
1561+
Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when
1562+
creating users and roles for authorization. It is case-sensitive and can only contain the following characters:
1563+
- alphanumeric characters (combination of a to z and 0 to 9)
1564+
- hyphens (-)
1565+
- underscores (_)
1566+
pattern: ^[a-zA-Z0-9-_]+$
1567+
type: string
1568+
groupsClaim:
1569+
default: groups
1570+
description: |-
1571+
The identifier of the claim that includes the principal's IdP user group membership information.
1572+
Accept the default value unless your IdP uses a different claim, or you need a custom claim.
1573+
Required when selected GroupMembership as the authorization type, ignored otherwise
1574+
type: string
1575+
issuerURI:
1576+
description: |-
1577+
Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider
1578+
Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint.
1579+
type: string
1580+
requestedScopes:
1581+
description: |-
1582+
Tokens that give users permission to request data from the authorization endpoint.
1583+
Only used for Workforce Identity Federation authorization method
1584+
items:
1585+
type: string
1586+
type: array
1587+
userClaim:
1588+
default: sub
1589+
description: |-
1590+
The identifier of the claim that includes the user principal identity.
1591+
Accept the default value unless your IdP uses a different claim.
1592+
type: string
1593+
required:
1594+
- audience
1595+
- authorizationMethod
1596+
- authorizationType
1597+
- configurationName
1598+
- issuerURI
1599+
- userClaim
1600+
type: object
1601+
type: array
15261602
requireClientTLSAuthentication:
15271603
description: Clients should present valid TLS certificates
15281604
type: boolean

‎helm_chart/crds/mongodb.com_mongodbmulticluster.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,8 +781,84 @@ spec:
781781
- MONGODB-CR
782782
- SCRAM-SHA-256
783783
- LDAP
784+
- OIDC
784785
type: string
785786
type: array
787+
oidcProviderConfigs:
788+
description: Configuration for OIDC providers
789+
items:
790+
properties:
791+
audience:
792+
description: |-
793+
Entity that your external identity provider intends the token for.
794+
Enter the audience value from the app you registered with external Identity Provider.
795+
type: string
796+
authorizationMethod:
797+
description: |-
798+
Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation.
799+
For programmatic, application access to Ops Manager deployments use Workload Identity Federation.
800+
Only one Workforce Identity Federation IdP can be configured per MongoDB resource
801+
enum:
802+
- WorkforceIdentityFederation
803+
- WorkloadIdentityFederation
804+
type: string
805+
authorizationType:
806+
description: |-
807+
Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant
808+
an individual user authorization.
809+
enum:
810+
- GroupMembership
811+
- UserID
812+
type: string
813+
clientId:
814+
description: |-
815+
Unique identifier for your registered application. Enter the clientId value from the app you
816+
registered with an external Identity Provider.
817+
Required when selected Workforce Identity Federation authorization method
818+
type: string
819+
configurationName:
820+
description: |-
821+
Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when
822+
creating users and roles for authorization. It is case-sensitive and can only contain the following characters:
823+
- alphanumeric characters (combination of a to z and 0 to 9)
824+
- hyphens (-)
825+
- underscores (_)
826+
pattern: ^[a-zA-Z0-9-_]+$
827+
type: string
828+
groupsClaim:
829+
default: groups
830+
description: |-
831+
The identifier of the claim that includes the principal's IdP user group membership information.
832+
Accept the default value unless your IdP uses a different claim, or you need a custom claim.
833+
Required when selected GroupMembership as the authorization type, ignored otherwise
834+
type: string
835+
issuerURI:
836+
description: |-
837+
Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider
838+
Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint.
839+
type: string
840+
requestedScopes:
841+
description: |-
842+
Tokens that give users permission to request data from the authorization endpoint.
843+
Only used for Workforce Identity Federation authorization method
844+
items:
845+
type: string
846+
type: array
847+
userClaim:
848+
default: sub
849+
description: |-
850+
The identifier of the claim that includes the user principal identity.
851+
Accept the default value unless your IdP uses a different claim.
852+
type: string
853+
required:
854+
- audience
855+
- authorizationMethod
856+
- authorizationType
857+
- configurationName
858+
- issuerURI
859+
- userClaim
860+
type: object
861+
type: array
786862
requireClientTLSAuthentication:
787863
description: Clients should present valid TLS certificates
788864
type: boolean

‎helm_chart/crds/mongodb.com_opsmanagers.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,8 +843,84 @@ spec:
843843
- MONGODB-CR
844844
- SCRAM-SHA-256
845845
- LDAP
846+
- OIDC
846847
type: string
847848
type: array
849+
oidcProviderConfigs:
850+
description: Configuration for OIDC providers
851+
items:
852+
properties:
853+
audience:
854+
description: |-
855+
Entity that your external identity provider intends the token for.
856+
Enter the audience value from the app you registered with external Identity Provider.
857+
type: string
858+
authorizationMethod:
859+
description: |-
860+
Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation.
861+
For programmatic, application access to Ops Manager deployments use Workload Identity Federation.
862+
Only one Workforce Identity Federation IdP can be configured per MongoDB resource
863+
enum:
864+
- WorkforceIdentityFederation
865+
- WorkloadIdentityFederation
866+
type: string
867+
authorizationType:
868+
description: |-
869+
Select GroupMembership to grant authorization based on IdP user group membership, or select UserID to grant
870+
an individual user authorization.
871+
enum:
872+
- GroupMembership
873+
- UserID
874+
type: string
875+
clientId:
876+
description: |-
877+
Unique identifier for your registered application. Enter the clientId value from the app you
878+
registered with an external Identity Provider.
879+
Required when selected Workforce Identity Federation authorization method
880+
type: string
881+
configurationName:
882+
description: |-
883+
Unique label that identifies this configuration. This label is visible to your Ops Manager users and is used when
884+
creating users and roles for authorization. It is case-sensitive and can only contain the following characters:
885+
- alphanumeric characters (combination of a to z and 0 to 9)
886+
- hyphens (-)
887+
- underscores (_)
888+
pattern: ^[a-zA-Z0-9-_]+$
889+
type: string
890+
groupsClaim:
891+
default: groups
892+
description: |-
893+
The identifier of the claim that includes the principal's IdP user group membership information.
894+
Accept the default value unless your IdP uses a different claim, or you need a custom claim.
895+
Required when selected GroupMembership as the authorization type, ignored otherwise
896+
type: string
897+
issuerURI:
898+
description: |-
899+
Issuer value provided by your registered IdP application. Using this URI, MongoDB finds an OpenID Provider
900+
Configuration Document, which should be available in the /.wellknown/open-id-configuration endpoint.
901+
type: string
902+
requestedScopes:
903+
description: |-
904+
Tokens that give users permission to request data from the authorization endpoint.
905+
Only used for Workforce Identity Federation authorization method
906+
items:
907+
type: string
908+
type: array
909+
userClaim:
910+
default: sub
911+
description: |-
912+
The identifier of the claim that includes the user principal identity.
913+
Accept the default value unless your IdP uses a different claim.
914+
type: string
915+
required:
916+
- audience
917+
- authorizationMethod
918+
- authorizationType
919+
- configurationName
920+
- issuerURI
921+
- userClaim
922+
type: object
923+
type: array
848924
requireClientTLSAuthentication:
849925
description: Clients should present valid TLS certificates
850926
type: boolean

‎pkg/util/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ const (
152152
MONGODBCR = "MONGODB-CR"
153153
SCRAMSHA256 = "SCRAM-SHA-256"
154154
LDAP = "LDAP"
155+
OIDC = "OIDC"
155156
MinimumScramSha256MdbVersion = "4.0.0"
156157

157158
// pprof variables

‎pkg/util/util.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/gob"
77
"encoding/hex"
88
"fmt"
9+
"net/url"
910
"regexp"
1011
"strings"
1112
"time"
@@ -184,3 +185,29 @@ func TransformToMap[T any, K comparable, V any](objs []T, f func(obj T, idx int)
184185
}
185186
return result
186187
}
188+
189+
// ParseURL checks if the given string is a valid URL and returns the parsed URL if valid.
190+
func ParseURL(str string) (*url.URL, error) {
191+
if strings.TrimSpace(str) == "" {
192+
return nil, fmt.Errorf("empty URL")
193+
}
194+
195+
u, err := url.Parse(str)
196+
if err != nil {
197+
return nil, fmt.Errorf("invalid URL: %w", err)
198+
}
199+
200+
if u.Scheme == "" {
201+
return nil, fmt.Errorf("missing URL scheme: %s", str)
202+
}
203+
204+
if u.Scheme != "http" && u.Scheme != "https" {
205+
return nil, fmt.Errorf("invalid URL scheme (http or https): %s", str)
206+
}
207+
208+
if u.Host == "" {
209+
return nil, fmt.Errorf("missing URL host: %s", str)
210+
}
211+
212+
return u, nil
213+
}

‎pkg/util/util_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
89

910
"github.com/mongodb/mongodb-kubernetes/pkg/util/identifiable"
1011
)
@@ -197,6 +198,69 @@ func TestTransformToMap(t *testing.T) {
197198
}))
198199
}
199200

201+
// TestIsURL tests the ParseURL function with various inputs.
202+
//
203+
//goland:noinspection HttpUrlsUsage
204+
func TestIsURL(t *testing.T) {
205+
tests := []struct {
206+
name string
207+
input string
208+
expectedErrorString string
209+
}{
210+
{
211+
name: "valid http URL",
212+
input: "http://example.com",
213+
},
214+
{
215+
name: "valid https URL with path",
216+
input: "https://example.com/path",
217+
},
218+
{
219+
name: "valid URL with port",
220+
input: "http://example.com:8080",
221+
},
222+
{
223+
name: "missing scheme",
224+
input: "example.com",
225+
expectedErrorString: "missing URL scheme: example.com",
226+
},
227+
{
228+
name: "missing host",
229+
input: "http://",
230+
expectedErrorString: "missing URL host: http://",
231+
},
232+
{
233+
name: "empty string",
234+
input: "",
235+
expectedErrorString: "empty URL",
236+
},
237+
{
238+
name: "invalid URL",
239+
input: ":invalid-url",
240+
expectedErrorString: "invalid URL: parse \":invalid-url\": missing protocol scheme",
241+
},
242+
{
243+
name: "file scheme",
244+
input: "file://path/to/file",
245+
expectedErrorString: "invalid URL scheme (http or https): file://path/to/file",
246+
},
247+
}
248+
249+
for _, tt := range tests {
250+
t.Run(tt.name, func(t *testing.T) {
251+
u, err := ParseURL(tt.input)
252+
if tt.expectedErrorString != "" {
253+
require.Error(t, err)
254+
assert.Equal(t, tt.expectedErrorString, err.Error())
255+
assert.Nil(t, u)
256+
} else {
257+
assert.NoError(t, err)
258+
assert.NotNil(t, u)
259+
}
260+
})
261+
}
262+
}
263+
200264
func pair(left, right identifiable.Identifiable) []identifiable.Identifiable {
201265
return []identifiable.Identifiable{left, right}
202266
}

‎public/crds.yaml

Lines changed: 228 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.