Skip to content

Commit f4465ab

Browse files
MaciejKaraslucian-tosaanandsyncs
authored
CLOUDP-314903 [OIDC] CRD Config Propagation to Automation Config (#60)
# Summary ### Core Functionality Enhancements: * Added a new authentication mechanism, `MongoDB-OIDC`, to the list of supported mechanisms in the `authentication_mechanism.go` file. * Introduced the `OIDCProviderConfigs` field in the `AutomationConfig` struct and implemented logic to merge and apply OIDC configurations into the deployment in the `automation_config.go` file. * Removed default value for `groupClaim` because the value `groups` can result in hard to debug misconfiguration. ### API and Configuration Updates: * Added the `IsOIDCEnabled()` method in the `Security` struct and `AuthResource` interface to check if OIDC is enabled. * Updated the `Options` struct in the `authentication.go` file to include `OIDCProviderConfigs`. ### Test Coverage: * Added comprehensive test cases for OIDC provider configurations in `automation_config_test.go`, including scenarios for merging, clearing, and modifying configurations. * Updated the `TestAutomationConfigEquality` test to include OIDC provider configurations. ### JSON Configuration Example: * Updated the `automation_config.json` test data file to include sample OIDC provider configurations for testing purposes. ## Proof of Work <!-- Enter your proof that it works here.--> ## Checklist - [ ] 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? ## Reminder (Please remove this when merging) - Please try to Approve or Reject Changes the PR, keep PRs in review as short as possible - Our Short Guide for PRs: [Link](https://docs.google.com/document/d/1T93KUtdvONq43vfTfUt8l92uo4e4SEEvFbIEKOxGr44/edit?tab=t.0) - Remember the following Communication Standards - use comment prefixes for clarity: * **blocking**: Must be addressed before approval. * **follow-up**: Can be addressed in a later PR or ticket. * **q**: Clarifying question. * **nit**: Non-blocking suggestions. * **note**: Side-note, non-actionable. Example: Praise * --> no prefix is considered a question --------- Co-authored-by: Lucian Tosa <lucian.tosa@mongodb.com> 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: Anand Singh <anand.singh@mongodb.com>
1 parent 212aaf2 commit f4465ab

35 files changed

+1058
-75
lines changed

api/v1/mdb/mongodb_types.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,9 +1097,8 @@ type OIDCProviderConfig struct {
10971097
// The identifier of the claim that includes the principal's IdP user group membership information.
10981098
// Accept the default value unless your IdP uses a different claim, or you need a custom claim.
10991099
// Required when selected GroupMembership as the authorization type, ignored otherwise
1100-
// +kubebuilder:default=groups
11011100
// +kubebuilder:validation:Optional
1102-
GroupsClaim string `json:"groupsClaim,omitempty"`
1101+
GroupsClaim *string `json:"groupsClaim"`
11031102

11041103
// Configure single-sign-on for human user access to Ops Manager deployments with Workforce Identity Federation.
11051104
// For programmatic, application access to Ops Manager deployments use Workload Identity Federation.
@@ -1111,7 +1110,7 @@ type OIDCProviderConfig struct {
11111110
// registered with an external Identity Provider.
11121111
// Required when selected Workforce Identity Federation authorization method
11131112
// +kubebuilder:validation:Optional
1114-
ClientId string `json:"clientId,omitempty"`
1113+
ClientId *string `json:"clientId"`
11151114

11161115
// Tokens that give users permission to request data from the authorization endpoint.
11171116
// Only used for Workforce Identity Federation authorization method

api/v1/mdb/mongodb_validation.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,11 @@ func oidcProviderConfigIssuerURIValidator(config OIDCProviderConfig) func(DbComm
256256
func oidcProviderConfigClientIdValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult {
257257
return func(_ DbCommonSpec) v1.ValidationResult {
258258
if config.AuthorizationMethod == OIDCAuthorizationMethodWorkforceIdentityFederation {
259-
if config.ClientId == "" {
259+
if config.ClientId == nil || *config.ClientId == "" {
260260
return v1.ValidationError("ClientId has to be specified in OIDC provider config %q with Workforce Identity Federation", config.ConfigurationName)
261261
}
262262
} else if config.AuthorizationMethod == OIDCAuthorizationMethodWorkloadIdentityFederation {
263-
if config.ClientId != "" {
263+
if config.ClientId != nil {
264264
return v1.ValidationWarning("ClientId will be ignored in OIDC provider config %q with Workload Identity Federation", config.ConfigurationName)
265265
}
266266
}
@@ -284,11 +284,11 @@ func oidcProviderConfigRequestedScopesValidator(config OIDCProviderConfig) func(
284284
func oidcProviderConfigAuthorizationTypeValidator(config OIDCProviderConfig) func(DbCommonSpec) v1.ValidationResult {
285285
return func(_ DbCommonSpec) v1.ValidationResult {
286286
if config.AuthorizationType == OIDCAuthorizationTypeGroupMembership {
287-
if config.GroupsClaim == "" {
287+
if config.GroupsClaim == nil || *config.GroupsClaim == "" {
288288
return v1.ValidationError("GroupsClaim has to be specified in OIDC provider config %q when using Group Membership authorization", config.ConfigurationName)
289289
}
290290
} else if config.AuthorizationType == OIDCAuthorizationTypeUserID {
291-
if config.GroupsClaim != "" {
291+
if config.GroupsClaim != nil {
292292
return v1.ValidationWarning("GroupsClaim will be ignored in OIDC provider config %q when using User ID authorization", config.ConfigurationName)
293293
}
294294
}
@@ -298,16 +298,14 @@ func oidcProviderConfigAuthorizationTypeValidator(config OIDCProviderConfig) fun
298298
}
299299

300300
func oidcAuthRequiresEnterprise(d DbCommonSpec) v1.ValidationResult {
301-
authSpec := d.Security.Authentication
302-
if authSpec != nil && authSpec.IsOIDCEnabled() && !strings.HasSuffix(d.Version, "-ent") {
301+
if d.Security.Authentication.IsOIDCEnabled() && !strings.HasSuffix(d.Version, "-ent") {
303302
return v1.ValidationError("Cannot enable OIDC authentication with MongoDB Community Builds")
304303
}
305304
return v1.ValidationSuccess()
306305
}
307306

308307
func ldapAuthRequiresEnterprise(d DbCommonSpec) v1.ValidationResult {
309-
authSpec := d.Security.Authentication
310-
if authSpec != nil && authSpec.IsLDAPEnabled() && !strings.HasSuffix(d.Version, "-ent") {
308+
if d.Security.Authentication.IsLDAPEnabled() && !strings.HasSuffix(d.Version, "-ent") {
311309
return v1.ValidationError("Cannot enable LDAP authentication with MongoDB Community Builds")
312310
}
313311
return v1.ValidationSuccess()

api/v1/mdb/mongodb_validation_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,13 @@ func TestOIDCAuthValidation(t *testing.T) {
258258
ConfigurationName: "provider",
259259
IssuerURI: "https://example1.com",
260260
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
261-
ClientId: "clientId1",
261+
ClientId: ptr.To("clientId1"),
262262
},
263263
{
264264
ConfigurationName: "provider",
265265
IssuerURI: "https://example2.com",
266266
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
267-
ClientId: "clientId2",
267+
ClientId: ptr.To("clientId2"),
268268
},
269269
},
270270
},
@@ -281,13 +281,13 @@ func TestOIDCAuthValidation(t *testing.T) {
281281
ConfigurationName: "test-provider1",
282282
IssuerURI: "https://example1.com",
283283
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
284-
ClientId: "clientId1",
284+
ClientId: ptr.To("clientId1"),
285285
},
286286
{
287287
ConfigurationName: "test-provider2",
288288
IssuerURI: "https://example2.com",
289289
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
290-
ClientId: "clientId2",
290+
ClientId: ptr.To("clientId2"),
291291
},
292292
},
293293
},
@@ -304,7 +304,7 @@ func TestOIDCAuthValidation(t *testing.T) {
304304
ConfigurationName: "test-provider-workforce1",
305305
IssuerURI: "https://example1.com",
306306
AuthorizationMethod: OIDCAuthorizationMethodWorkforceIdentityFederation,
307-
ClientId: "clientId1",
307+
ClientId: ptr.To("clientId1"),
308308
},
309309
{
310310
ConfigurationName: "test-provider-workload2",
@@ -376,7 +376,7 @@ func TestOIDCAuthValidation(t *testing.T) {
376376
ConfigurationName: "test-provider",
377377
IssuerURI: "https://example.com",
378378
AuthorizationMethod: OIDCAuthorizationMethodWorkloadIdentityFederation,
379-
ClientId: "clientId",
379+
ClientId: ptr.To("clientId"),
380380
},
381381
},
382382
},
@@ -410,7 +410,7 @@ func TestOIDCAuthValidation(t *testing.T) {
410410
ConfigurationName: "test-provider1",
411411
IssuerURI: "https://example.com",
412412
AuthorizationType: OIDCAuthorizationTypeGroupMembership,
413-
GroupsClaim: "groups",
413+
GroupsClaim: ptr.To("groups"),
414414
},
415415
{
416416
ConfigurationName: "test-provider2",
@@ -432,7 +432,7 @@ func TestOIDCAuthValidation(t *testing.T) {
432432
ConfigurationName: "test-provider1",
433433
IssuerURI: "https://example.com",
434434
AuthorizationType: OIDCAuthorizationTypeUserID,
435-
GroupsClaim: "groups",
435+
GroupsClaim: ptr.To("groups"),
436436
UserClaim: "sub",
437437
},
438438
{
@@ -456,13 +456,13 @@ func TestOIDCAuthValidation(t *testing.T) {
456456
ConfigurationName: "test-provider1",
457457
IssuerURI: "https://example.com",
458458
AuthorizationType: OIDCAuthorizationTypeGroupMembership,
459-
GroupsClaim: "groups",
459+
GroupsClaim: ptr.To("groups"),
460460
},
461461
{
462462
ConfigurationName: "test-provider2",
463463
IssuerURI: "https://example.com",
464464
AuthorizationType: OIDCAuthorizationTypeGroupMembership,
465-
GroupsClaim: "groups",
465+
GroupsClaim: ptr.To("groups"),
466466
},
467467
},
468468
},

api/v1/mdb/zz_generated.deepcopy.go

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/mongodb.com_mongodb.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1566,7 +1566,6 @@ spec:
15661566
pattern: ^[a-zA-Z0-9-_]+$
15671567
type: string
15681568
groupsClaim:
1569-
default: groups
15701569
description: |-
15711570
The identifier of the claim that includes the principal's IdP user group membership information.
15721571
Accept the default value unless your IdP uses a different claim, or you need a custom claim.

config/crd/bases/mongodb.com_mongodbmulticluster.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,6 @@ spec:
826826
pattern: ^[a-zA-Z0-9-_]+$
827827
type: string
828828
groupsClaim:
829-
default: groups
830829
description: |-
831830
The identifier of the claim that includes the principal's IdP user group membership information.
832831
Accept the default value unless your IdP uses a different claim, or you need a custom claim.

config/crd/bases/mongodb.com_opsmanagers.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,6 @@ spec:
888888
pattern: ^[a-zA-Z0-9-_]+$
889889
type: string
890890
groupsClaim:
891-
default: groups
892891
description: |-
893892
The identifier of the claim that includes the principal's IdP user group membership information.
894893
Accept the default value unless your IdP uses a different claim, or you need a custom claim.

controllers/om/automation_config.go

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"k8s.io/apimachinery/pkg/api/equality"
99

1010
"github.com/mongodb/mongodb-kubernetes/controllers/operator/ldap"
11+
"github.com/mongodb/mongodb-kubernetes/controllers/operator/oidc"
1112
"github.com/mongodb/mongodb-kubernetes/pkg/util"
1213
"github.com/mongodb/mongodb-kubernetes/pkg/util/generate"
1314
"github.com/mongodb/mongodb-kubernetes/pkg/util/maputil"
@@ -20,10 +21,11 @@ import (
2021
// configuration which are merged into the `Deployment` object before sending it back to Ops Manager.
2122
// As of right now only support configuring LogRotate for monitoring and backup via dedicated endpoints.
2223
type AutomationConfig struct {
23-
Auth *Auth
24-
AgentSSL *AgentSSL
25-
Deployment Deployment
26-
Ldap *ldap.Ldap
24+
Auth *Auth
25+
AgentSSL *AgentSSL
26+
Deployment Deployment
27+
Ldap *ldap.Ldap
28+
OIDCProviderConfigs []oidc.ProviderConfig
2729
}
2830

2931
// Apply merges the state of all concrete structs into the Deployment (map[string]interface{})
@@ -58,9 +60,66 @@ func applyInto(a AutomationConfig, into *Deployment) error {
5860
}
5961
(*into)["ldap"] = mergedLdap
6062
}
63+
64+
if len(a.OIDCProviderConfigs) > 0 {
65+
updateOIDCProviderConfigs(a, into)
66+
} else {
67+
// Clear oidcProviderConfigs if no configs are provided
68+
delete(*into, "oidcProviderConfigs")
69+
}
70+
6171
return nil
6272
}
6373

74+
func updateOIDCProviderConfigs(a AutomationConfig, into *Deployment) {
75+
deploymentConfigs := make(map[string]map[string]any)
76+
if configs, ok := a.Deployment["oidcProviderConfigs"]; ok {
77+
configsSliceAny := cast.ToSlice(configs)
78+
for _, configAny := range configsSliceAny {
79+
config := configAny.(map[string]any)
80+
configName := config["authNamePrefix"].(string)
81+
deploymentConfigs[configName] = config
82+
}
83+
}
84+
85+
result := make([]map[string]any, 0)
86+
for _, config := range a.OIDCProviderConfigs {
87+
deploymentConfig, ok := deploymentConfigs[config.AuthNamePrefix]
88+
if !ok {
89+
deploymentConfig = make(map[string]any)
90+
}
91+
92+
deploymentConfig["authNamePrefix"] = config.AuthNamePrefix
93+
deploymentConfig["audience"] = config.Audience
94+
deploymentConfig["issuerUri"] = config.IssuerUri
95+
deploymentConfig["userClaim"] = config.UserClaim
96+
deploymentConfig["supportsHumanFlows"] = config.SupportsHumanFlows
97+
deploymentConfig["useAuthorizationClaim"] = config.UseAuthorizationClaim
98+
99+
if config.ClientId == nil {
100+
delete(deploymentConfig, "clientId")
101+
} else {
102+
deploymentConfig["clientId"] = config.ClientId
103+
}
104+
105+
if len(config.RequestedScopes) == 0 {
106+
delete(deploymentConfig, "requestedScopes")
107+
} else {
108+
deploymentConfig["requestedScopes"] = config.RequestedScopes
109+
}
110+
111+
if config.GroupsClaim == nil {
112+
delete(deploymentConfig, "groupsClaim")
113+
} else {
114+
deploymentConfig["groupsClaim"] = config.GroupsClaim
115+
}
116+
117+
result = append(result, deploymentConfig)
118+
}
119+
120+
(*into)["oidcProviderConfigs"] = result
121+
}
122+
64123
// EqualsWithoutDeployment returns true if two AutomationConfig objects are meaningful equal by following the following conditions:
65124
// - Not taking AutomationConfig.Deployment into consideration.
66125
// - Serializing ac A and ac B to ensure that we remove util.MergoDelete before comparing those two.
@@ -432,6 +491,19 @@ func BuildAutomationConfigFromDeployment(deployment Deployment) (*AutomationConf
432491
finalAutomationConfig.Ldap = acLdap
433492
}
434493

494+
oidcConfigsArray, ok := deployment["oidcProviderConfigs"]
495+
if ok {
496+
oidcMarshalled, err := json.Marshal(oidcConfigsArray)
497+
if err != nil {
498+
return nil, err
499+
}
500+
providerConfigs := make([]oidc.ProviderConfig, 0)
501+
if err := json.Unmarshal(oidcMarshalled, &providerConfigs); err != nil {
502+
return nil, err
503+
}
504+
finalAutomationConfig.OIDCProviderConfigs = providerConfigs
505+
}
506+
435507
return finalAutomationConfig, nil
436508
}
437509

0 commit comments

Comments
 (0)