Skip to content

Commit 042cceb

Browse files
committed
fixup! update
Signed-off-by: Bryce Palmer <[email protected]>
1 parent 1b757c1 commit 042cceb

File tree

103 files changed

+4004
-486
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+4004
-486
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/ghodss/yaml v1.0.0
1010
github.com/golang-jwt/jwt/v5 v5.2.1
1111
github.com/google/go-cmp v0.6.0
12-
github.com/openshift/api v0.0.0-20250305225826-b8da3bfeaf77
12+
github.com/openshift/api v0.0.0-20250602100215-91245a26b8a8
1313
github.com/openshift/build-machinery-go v0.0.0-20250102153059-e85a1a7ecb5c
1414
github.com/openshift/client-go v0.0.0-20250125113824-8e1f0b8fa9a7
1515
github.com/openshift/library-go v0.0.0-20250319141325-07c53d93ad06

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM
149149
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
150150
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
151151
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
152-
github.com/openshift/api v0.0.0-20250305225826-b8da3bfeaf77 h1:w6F0sEhlUB1K54Ev4EELsLo5w/xur9pFT19VtemlB4Y=
153-
github.com/openshift/api v0.0.0-20250305225826-b8da3bfeaf77/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw=
152+
github.com/openshift/api v0.0.0-20250602100215-91245a26b8a8 h1:fylJknGtyE1rKkYki3O1eF1m20qidXYku3ij8cnZtCc=
153+
github.com/openshift/api v0.0.0-20250602100215-91245a26b8a8/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw=
154154
github.com/openshift/build-machinery-go v0.0.0-20250102153059-e85a1a7ecb5c h1:6XcszPFZpan4qll5XbdLll7n1So3IsPn28aw2j1obMo=
155155
github.com/openshift/build-machinery-go v0.0.0-20250102153059-e85a1a7ecb5c/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE=
156156
github.com/openshift/client-go v0.0.0-20250125113824-8e1f0b8fa9a7 h1:4iliLcvr1P9EUMZgIaSNEKNQQzBn+L6PSequlFOuB6Q=

pkg/controllers/externaloidc/externaloidc_controller.go

Lines changed: 87 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import (
1414
"time"
1515

1616
configv1 "github.com/openshift/api/config/v1"
17+
"github.com/openshift/api/features"
1718
configinformers "github.com/openshift/client-go/config/informers/externalversions"
1819
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
1920
"github.com/openshift/library-go/pkg/controller/factory"
21+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
2022
"github.com/openshift/library-go/pkg/operator/events"
2123
"github.com/openshift/library-go/pkg/operator/resource/retry"
2224
"github.com/openshift/library-go/pkg/operator/v1helpers"
@@ -25,7 +27,10 @@ import (
2527
"k8s.io/apimachinery/pkg/api/equality"
2628
apierrors "k8s.io/apimachinery/pkg/api/errors"
2729
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apiserver/pkg/apis/apiserver"
2831
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"
2934
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
3035
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
3136
corev1listers "k8s.io/client-go/listers/core/v1"
@@ -48,6 +53,7 @@ type externalOIDCController struct {
4853
authLister configv1listers.AuthenticationLister
4954
configMapLister corev1listers.ConfigMapLister
5055
configMaps corev1client.ConfigMapsGetter
56+
featureGates featuregates.FeatureGate
5157
}
5258

5359
func NewExternalOIDCController(
@@ -56,6 +62,7 @@ func NewExternalOIDCController(
5662
operatorClient v1helpers.OperatorClient,
5763
configMaps corev1client.ConfigMapsGetter,
5864
recorder events.Recorder,
65+
featureGates featuregates.FeatureGate,
5966
) factory.Controller {
6067
c := &externalOIDCController{
6168
name: "ExternalOIDCController",
@@ -64,6 +71,7 @@ func NewExternalOIDCController(
6471
authLister: configInformer.Config().V1().Authentications().Lister(),
6572
configMapLister: kubeInformersForNamespaces.ConfigMapLister(),
6673
configMaps: configMaps,
74+
featureGates: featureGates,
6775
}
6876

6977
return factory.New().WithInformers(
@@ -110,7 +118,7 @@ func (c *externalOIDCController) sync(ctx context.Context, syncCtx factory.SyncC
110118
return nil
111119
}
112120

113-
if err := validateAuthConfig(*authConfig); err != nil {
121+
if err := validateAuthConfig(*authConfig, []string{auth.Spec.ServiceAccountIssuer}); err != nil {
114122
return fmt.Errorf("auth config validation failed: %v", err)
115123
}
116124

@@ -153,7 +161,7 @@ func (c *externalOIDCController) generateAuthConfig(auth configv1.Authentication
153161

154162
errs := []error{}
155163
for _, provider := range auth.Spec.OIDCProviders {
156-
jwt, err := generateJWTForProvider(provider, c.configMapLister)
164+
jwt, err := generateJWTForProvider(provider, c.configMapLister, c.featureGates)
157165
if err != nil {
158166
errs = append(errs, err)
159167
continue
@@ -169,14 +177,15 @@ func (c *externalOIDCController) generateAuthConfig(auth configv1.Authentication
169177
return &authConfig, nil
170178
}
171179

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) {
173181
out := apiserverv1beta1.JWTAuthenticator{}
182+
174183
issuer, err := generateIssuer(provider.Issuer, configMapLister)
175184
if err != nil {
176185
return out, fmt.Errorf("generating issuer for provider %q: %v", provider.Name, err)
177186
}
178187

179-
claimMappings, err := generateClaimMappings(provider.ClaimMappings, issuer.URL)
188+
claimMappings, err := generateClaimMappings(provider.ClaimMappings, issuer.URL, featureGates)
180189
if err != nil {
181190
return out, fmt.Errorf("generating claimMappings for provider %q: %v", provider.Name, err)
182191
}
@@ -228,28 +237,35 @@ func getCertificateAuthorityFromConfigMap(name string, configMapLister corev1lis
228237
return caData, nil
229238
}
230239

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+
232243
username, err := generateUsernameClaimMapping(claimMappings.Username, issuerURL)
233244
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)
235246
}
236247

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)
241249

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
245266
}
246267

247-
return apiserverv1beta1.ClaimMappings{
248-
Username: username,
249-
Groups: generateGroupsClaimMapping(claimMappings.Groups),
250-
UID: uid,
251-
Extra: extras,
252-
}, nil
268+
return out, nil
253269
}
254270

255271
func generateUsernameClaimMapping(usernameClaimMapping configv1.UsernameClaimMapping, issuerURL string) (apiserverv1beta1.PrefixedClaimOrExpression, error) {
@@ -258,6 +274,9 @@ func generateUsernameClaimMapping(usernameClaimMapping configv1.UsernameClaimMap
258274
// Currently, the authentications.config.openshift.io CRD only allows setting a claim for the mapping
259275
// and does not allow setting a CEL expression like the upstream. This is likely to change in the future,
260276
// 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+
}
261280
out.Claim = usernameClaimMapping.Claim
262281

263282
switch usernameClaimMapping.PrefixPolicy {
@@ -298,29 +317,31 @@ func generateGroupsClaimMapping(groupsMapping configv1.PrefixedClaimMapping) api
298317
return out
299318
}
300319

301-
func generateUIDClaimMapping(uidMapping configv1.UIDClaimMapping) (apiserverv1beta1.ClaimOrExpression, error) {
320+
func generateUIDClaimMapping(uid *configv1.TokenClaimOrExpressionMapping) (apiserverv1beta1.ClaimOrExpression, error) {
302321
out := apiserverv1beta1.ClaimOrExpression{}
303322

304323
// UID mapping can only specify either claim or expression, not both.
305324
// This should be rejected at admission time of the authentications.config.openshift.io CRD.
306325
// Even though this is the case, we still perform a runtime validation to ensure we never
307326
// attempt to create an invalid configuration.
327+
// If neither claim or expression is specified, default the claim to "sub"
308328
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:
317330
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)
318339
}
319340

320341
return out, nil
321342
}
322343

323-
func generateExtraMappings(extraMappings ...configv1.ExtraMapping) ([]apiserverv1beta1.ExtraMapping, error) {
344+
func generateExtraClaimMapping(extraMappings ...configv1.ExtraMapping) ([]apiserverv1beta1.ExtraMapping, error) {
324345
out := []apiserverv1beta1.ExtraMapping{}
325346
errs := []error{}
326347
for _, extraMapping := range extraMappings {
@@ -383,6 +404,8 @@ func generateClaimValidationRule(claimValidationRule configv1.TokenClaimValidati
383404

384405
out.Claim = claimValidationRule.RequiredClaim.Claim
385406
out.RequiredValue = claimValidationRule.RequiredClaim.RequiredValue
407+
default:
408+
return out, fmt.Errorf("unknown claimValidationRule type %q", claimValidationRule.Type)
386409
}
387410

388411
return out, nil
@@ -422,10 +445,26 @@ func (c *externalOIDCController) getExistingApplyConfig() (*corev1ac.ConfigMapAp
422445
return existingCMApplyConfig, nil
423446
}
424447

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+
429468
for _, jwt := range auth.JWT {
430469
var caCertPool *x509.CertPool
431470
var err error
@@ -495,3 +534,18 @@ func validateCACert(hostURL string, caCertPool *x509.CertPool) error {
495534

496535
return nil
497536
}
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

Comments
 (0)