@@ -14,9 +14,11 @@ import (
14
14
"time"
15
15
16
16
configv1 "github.com/openshift/api/config/v1"
17
+ "github.com/openshift/api/features"
17
18
configinformers "github.com/openshift/client-go/config/informers/externalversions"
18
19
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
19
20
"github.com/openshift/library-go/pkg/controller/factory"
21
+ "github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
20
22
"github.com/openshift/library-go/pkg/operator/events"
21
23
"github.com/openshift/library-go/pkg/operator/resource/retry"
22
24
"github.com/openshift/library-go/pkg/operator/v1helpers"
@@ -25,7 +27,10 @@ import (
25
27
"k8s.io/apimachinery/pkg/api/equality"
26
28
apierrors "k8s.io/apimachinery/pkg/api/errors"
27
29
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30
+ "k8s.io/apiserver/pkg/apis/apiserver"
28
31
apiserverv1beta1 "k8s.io/apiserver/pkg/apis/apiserver/v1beta1"
32
+ apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
33
+ authenticationcel "k8s.io/apiserver/pkg/authentication/cel"
29
34
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
30
35
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
31
36
corev1listers "k8s.io/client-go/listers/core/v1"
@@ -48,6 +53,7 @@ type externalOIDCController struct {
48
53
authLister configv1listers.AuthenticationLister
49
54
configMapLister corev1listers.ConfigMapLister
50
55
configMaps corev1client.ConfigMapsGetter
56
+ featureGates featuregates.FeatureGate
51
57
}
52
58
53
59
func NewExternalOIDCController (
@@ -56,6 +62,7 @@ func NewExternalOIDCController(
56
62
operatorClient v1helpers.OperatorClient ,
57
63
configMaps corev1client.ConfigMapsGetter ,
58
64
recorder events.Recorder ,
65
+ featureGates featuregates.FeatureGate ,
59
66
) factory.Controller {
60
67
c := & externalOIDCController {
61
68
name : "ExternalOIDCController" ,
@@ -64,6 +71,7 @@ func NewExternalOIDCController(
64
71
authLister : configInformer .Config ().V1 ().Authentications ().Lister (),
65
72
configMapLister : kubeInformersForNamespaces .ConfigMapLister (),
66
73
configMaps : configMaps ,
74
+ featureGates : featureGates ,
67
75
}
68
76
69
77
return factory .New ().WithInformers (
@@ -110,7 +118,7 @@ func (c *externalOIDCController) sync(ctx context.Context, syncCtx factory.SyncC
110
118
return nil
111
119
}
112
120
113
- if err := validateAuthConfig (* authConfig ); err != nil {
121
+ if err := validateAuthConfig (* authConfig , [] string { auth . Spec . ServiceAccountIssuer } ); err != nil {
114
122
return fmt .Errorf ("auth config validation failed: %v" , err )
115
123
}
116
124
@@ -153,7 +161,7 @@ func (c *externalOIDCController) generateAuthConfig(auth configv1.Authentication
153
161
154
162
errs := []error {}
155
163
for _ , provider := range auth .Spec .OIDCProviders {
156
- jwt , err := generateJWTForProvider (provider , c .configMapLister )
164
+ jwt , err := generateJWTForProvider (provider , c .configMapLister , c . featureGates )
157
165
if err != nil {
158
166
errs = append (errs , err )
159
167
continue
@@ -169,14 +177,15 @@ func (c *externalOIDCController) generateAuthConfig(auth configv1.Authentication
169
177
return & authConfig , nil
170
178
}
171
179
172
- func generateJWTForProvider (provider configv1.OIDCProvider , configMapLister corev1listers.ConfigMapLister ) (apiserverv1beta1.JWTAuthenticator , error ) {
180
+ func generateJWTForProvider (provider configv1.OIDCProvider , configMapLister corev1listers.ConfigMapLister , featureGates featuregates. FeatureGate ) (apiserverv1beta1.JWTAuthenticator , error ) {
173
181
out := apiserverv1beta1.JWTAuthenticator {}
182
+
174
183
issuer , err := generateIssuer (provider .Issuer , configMapLister )
175
184
if err != nil {
176
185
return out , fmt .Errorf ("generating issuer for provider %q: %v" , provider .Name , err )
177
186
}
178
187
179
- claimMappings , err := generateClaimMappings (provider .ClaimMappings , issuer .URL )
188
+ claimMappings , err := generateClaimMappings (provider .ClaimMappings , issuer .URL , featureGates )
180
189
if err != nil {
181
190
return out , fmt .Errorf ("generating claimMappings for provider %q: %v" , provider .Name , err )
182
191
}
@@ -228,28 +237,35 @@ func getCertificateAuthorityFromConfigMap(name string, configMapLister corev1lis
228
237
return caData , nil
229
238
}
230
239
231
- func generateClaimMappings (claimMappings configv1.TokenClaimMappings , issuerURL string ) (apiserverv1beta1.ClaimMappings , error ) {
240
+ func generateClaimMappings (claimMappings configv1.TokenClaimMappings , issuerURL string , featureGates featuregates.FeatureGate ) (apiserverv1beta1.ClaimMappings , error ) {
241
+ out := apiserverv1beta1.ClaimMappings {}
242
+
232
243
username , err := generateUsernameClaimMapping (claimMappings .Username , issuerURL )
233
244
if err != nil {
234
- return apiserverv1beta1. ClaimMappings {} , fmt .Errorf ("generating username claim mapping: %v" , err )
245
+ return out , fmt .Errorf ("generating username claim mapping: %v" , err )
235
246
}
236
247
237
- uid , err := generateUIDClaimMapping (claimMappings .UID )
238
- if err != nil {
239
- return apiserverv1beta1.ClaimMappings {}, fmt .Errorf ("generating uid claim mapping: %v" , err )
240
- }
248
+ groups := generateGroupsClaimMapping (claimMappings .Groups )
241
249
242
- extras , err := generateExtraMappings (claimMappings .Extra ... )
243
- if err != nil {
244
- return apiserverv1beta1.ClaimMappings {}, fmt .Errorf ("generating extra mappings: %v" , err )
250
+ out .Username = username
251
+ out .Groups = groups
252
+
253
+ if featureGates .Enabled (features .FeatureGateExternalOIDCWithAdditionalClaimMappings ) {
254
+ uid , err := generateUIDClaimMapping (claimMappings .UID )
255
+ if err != nil {
256
+ return out , fmt .Errorf ("generating uid claim mapping: %v" , err )
257
+ }
258
+
259
+ extras , err := generateExtraClaimMapping (claimMappings .Extra ... )
260
+ if err != nil {
261
+ return out , fmt .Errorf ("generating extra claim mapping: %v" , err )
262
+ }
263
+
264
+ out .UID = uid
265
+ out .Extra = extras
245
266
}
246
267
247
- return apiserverv1beta1.ClaimMappings {
248
- Username : username ,
249
- Groups : generateGroupsClaimMapping (claimMappings .Groups ),
250
- UID : uid ,
251
- Extra : extras ,
252
- }, nil
268
+ return out , nil
253
269
}
254
270
255
271
func generateUsernameClaimMapping (usernameClaimMapping configv1.UsernameClaimMapping , issuerURL string ) (apiserverv1beta1.PrefixedClaimOrExpression , error ) {
@@ -258,6 +274,9 @@ func generateUsernameClaimMapping(usernameClaimMapping configv1.UsernameClaimMap
258
274
// Currently, the authentications.config.openshift.io CRD only allows setting a claim for the mapping
259
275
// and does not allow setting a CEL expression like the upstream. This is likely to change in the future,
260
276
// but for now just set the claim.
277
+ if usernameClaimMapping .Claim == "" {
278
+ return out , fmt .Errorf ("username claim is required but an empty value was provided" )
279
+ }
261
280
out .Claim = usernameClaimMapping .Claim
262
281
263
282
switch usernameClaimMapping .PrefixPolicy {
@@ -298,29 +317,31 @@ func generateGroupsClaimMapping(groupsMapping configv1.PrefixedClaimMapping) api
298
317
return out
299
318
}
300
319
301
- func generateUIDClaimMapping (uidMapping configv1.UIDClaimMapping ) (apiserverv1beta1.ClaimOrExpression , error ) {
320
+ func generateUIDClaimMapping (uid * configv1.TokenClaimOrExpressionMapping ) (apiserverv1beta1.ClaimOrExpression , error ) {
302
321
out := apiserverv1beta1.ClaimOrExpression {}
303
322
304
323
// UID mapping can only specify either claim or expression, not both.
305
324
// This should be rejected at admission time of the authentications.config.openshift.io CRD.
306
325
// Even though this is the case, we still perform a runtime validation to ensure we never
307
326
// attempt to create an invalid configuration.
327
+ // If neither claim or expression is specified, default the claim to "sub"
308
328
switch {
309
- case uidMapping .Claim != "" && uidMapping .Expression == "" :
310
- out .Claim = uidMapping .Claim
311
- case uidMapping .Expression != "" && uidMapping .Claim == "" :
312
- out .Expression = uidMapping .Expression
313
- case uidMapping .Claim != "" && uidMapping .Expression != "" :
314
- return out , fmt .Errorf ("invalid uid mapping provided. must set either claim or expression, not both: %v" , uidMapping )
315
- default :
316
- // by default, if there is no uid mapping provided we set it to the "sub" claim
329
+ case uid == nil :
317
330
out .Claim = "sub"
331
+ case uid .Claim != "" && uid .Expression == "" :
332
+ out .Claim = uid .Claim
333
+ case uid .Expression != "" && uid .Claim == "" :
334
+ out .Expression = uid .Expression
335
+ case uid .Claim != "" && uid .Expression != "" :
336
+ return out , fmt .Errorf ("uid mapping must set either claim or expression, not both: %v" , uid )
337
+ default :
338
+ return out , fmt .Errorf ("unable to handle uid mapping: %v" , uid )
318
339
}
319
340
320
341
return out , nil
321
342
}
322
343
323
- func generateExtraMappings (extraMappings ... configv1.ExtraMapping ) ([]apiserverv1beta1.ExtraMapping , error ) {
344
+ func generateExtraClaimMapping (extraMappings ... configv1.ExtraMapping ) ([]apiserverv1beta1.ExtraMapping , error ) {
324
345
out := []apiserverv1beta1.ExtraMapping {}
325
346
errs := []error {}
326
347
for _ , extraMapping := range extraMappings {
@@ -383,6 +404,8 @@ func generateClaimValidationRule(claimValidationRule configv1.TokenClaimValidati
383
404
384
405
out .Claim = claimValidationRule .RequiredClaim .Claim
385
406
out .RequiredValue = claimValidationRule .RequiredClaim .RequiredValue
407
+ default :
408
+ return out , fmt .Errorf ("unknown claimValidationRule type %q" , claimValidationRule .Type )
386
409
}
387
410
388
411
return out , nil
@@ -422,10 +445,26 @@ func (c *externalOIDCController) getExistingApplyConfig() (*corev1ac.ConfigMapAp
422
445
return existingCMApplyConfig , nil
423
446
}
424
447
425
- // validateAuthConfig performs validations that are not done at the server-side,
426
- // including validation that the provided CA cert (or system CAs if not specified) can be used for
427
- // TLS cert verification.
428
- func validateAuthConfig (auth apiserverv1beta1.AuthenticationConfiguration ) error {
448
+ // validateAuthConfig ensures that the generated authentication configuration is valid.
449
+ // It performs:
450
+ // - The same validations as the Kubernetes API server
451
+ // - Validations not done by the Kubernetes API server
452
+ // - Verifies that the provided CA cert can be used for TLS cert verification
453
+ func validateAuthConfig (auth apiserverv1beta1.AuthenticationConfiguration , disallowIssuers []string ) error {
454
+ // Validate the Structured Authentication Configuration using the same library
455
+ // that the Kubernetes API server uses to ensure we are performing the same validations
456
+ // earlier in the chain and never create an invalid configuration.
457
+ apiServerAuthConfig , err := generatedAuthConfigToKASInternalAuthConfig (& auth )
458
+ if err != nil {
459
+ return fmt .Errorf ("converting from generated auth config to kube-apiserver internal auth config: %w" , err )
460
+ }
461
+
462
+ celCompiler := authenticationcel .NewDefaultCompiler ()
463
+ fieldErrors := apiservervalidation .ValidateAuthenticationConfiguration (celCompiler , apiServerAuthConfig , disallowIssuers )
464
+ if err := fieldErrors .ToAggregate (); err != nil {
465
+ return fmt .Errorf ("validating generated auth config: %w" , err )
466
+ }
467
+
429
468
for _ , jwt := range auth .JWT {
430
469
var caCertPool * x509.CertPool
431
470
var err error
@@ -495,3 +534,18 @@ func validateCACert(hostURL string, caCertPool *x509.CertPool) error {
495
534
496
535
return nil
497
536
}
537
+
538
+ func generatedAuthConfigToKASInternalAuthConfig (generated * apiserverv1beta1.AuthenticationConfiguration ) (* apiserver.AuthenticationConfiguration , error ) {
539
+ outBytes , err := json .Marshal (generated )
540
+ if err != nil {
541
+ return nil , fmt .Errorf ("marshalling generated auth config to JSON: %v" , err )
542
+ }
543
+
544
+ apiserverAuthConfig := & apiserver.AuthenticationConfiguration {}
545
+ err = json .Unmarshal (outBytes , apiserverAuthConfig )
546
+ if err != nil {
547
+ return nil , fmt .Errorf ("unmarshalling generated auth config JSON to apiserver auth config: %v" , err )
548
+ }
549
+
550
+ return apiserverAuthConfig , nil
551
+ }
0 commit comments