Skip to content

Commit 90ee689

Browse files
authored
Merge pull request kubernetes#127504 from sttts/sttts-authz-cel-compiler-once
apiserver/authconfig: make CEL compiler shareable
2 parents 48f36ac + c44fc28 commit 90ee689

File tree

16 files changed

+184
-158
lines changed

16 files changed

+184
-158
lines changed

pkg/kubeapiserver/authorizer/config.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"k8s.io/apiserver/pkg/apis/apiserver/load"
3232
"k8s.io/apiserver/pkg/apis/apiserver/validation"
3333
"k8s.io/apiserver/pkg/authorization/authorizer"
34+
authorizationcel "k8s.io/apiserver/pkg/authorization/cel"
3435
utilfeature "k8s.io/apiserver/pkg/util/feature"
3536
versionedinformers "k8s.io/client-go/informers"
3637
resourceinformers "k8s.io/client-go/informers/resource/v1alpha3"
@@ -72,6 +73,8 @@ type Config struct {
7273
// New returns the right sort of union of multiple authorizer.Authorizer objects
7374
// based on the authorizationMode or an error.
7475
// stopCh is used to shut down config reload goroutines when the server is shutting down.
76+
//
77+
// Note: the cel compiler construction depends on feature gates and the compatibility version to be initialized.
7578
func (config Config) New(ctx context.Context, serverID string) (authorizer.Authorizer, authorizer.RuleResolver, error) {
7679
if len(config.AuthorizationConfiguration.Authorizers) == 0 {
7780
return nil, nil, fmt.Errorf("at least one authorization mode must be passed")
@@ -82,6 +85,7 @@ func (config Config) New(ctx context.Context, serverID string) (authorizer.Autho
8285
apiServerID: serverID,
8386
lastLoadedConfig: config.AuthorizationConfiguration,
8487
reloadInterval: time.Minute,
88+
compiler: authorizationcel.NewDefaultCompiler(),
8589
}
8690

8791
seenTypes := sets.New[authzconfig.AuthorizerType]()
@@ -156,25 +160,25 @@ func GetNameForAuthorizerMode(mode string) string {
156160
return strings.ToLower(mode)
157161
}
158162

159-
func LoadAndValidateFile(configFile string, requireNonWebhookTypes sets.Set[authzconfig.AuthorizerType]) (*authzconfig.AuthorizationConfiguration, error) {
163+
func LoadAndValidateFile(configFile string, compiler authorizationcel.Compiler, requireNonWebhookTypes sets.Set[authzconfig.AuthorizerType]) (*authzconfig.AuthorizationConfiguration, error) {
160164
data, err := os.ReadFile(configFile)
161165
if err != nil {
162166
return nil, err
163167
}
164-
return LoadAndValidateData(data, requireNonWebhookTypes)
168+
return LoadAndValidateData(data, compiler, requireNonWebhookTypes)
165169
}
166170

167-
func LoadAndValidateData(data []byte, requireNonWebhookTypes sets.Set[authzconfig.AuthorizerType]) (*authzconfig.AuthorizationConfiguration, error) {
171+
func LoadAndValidateData(data []byte, compiler authorizationcel.Compiler, requireNonWebhookTypes sets.Set[authzconfig.AuthorizerType]) (*authzconfig.AuthorizationConfiguration, error) {
168172
// load the file and check for errors
169173
authorizationConfiguration, err := load.LoadFromData(data)
170174
if err != nil {
171175
return nil, fmt.Errorf("failed to load AuthorizationConfiguration from file: %w", err)
172176
}
173177

174178
// validate the file and return any error
175-
if errors := validation.ValidateAuthorizationConfiguration(nil, authorizationConfiguration,
176-
sets.NewString(modes.AuthorizationModeChoices...),
177-
sets.NewString(repeatableAuthorizerTypes...),
179+
if errors := validation.ValidateAuthorizationConfiguration(compiler, nil, authorizationConfiguration,
180+
sets.New(modes.AuthorizationModeChoices...),
181+
sets.New(repeatableAuthorizerTypes...),
178182
); len(errors) != 0 {
179183
return nil, errors.ToAggregate()
180184
}

pkg/kubeapiserver/authorizer/reload.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
"k8s.io/apiserver/pkg/authentication/user"
3333
"k8s.io/apiserver/pkg/authorization/authorizer"
3434
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
35-
"k8s.io/apiserver/pkg/authorization/cel"
35+
authorizationcel "k8s.io/apiserver/pkg/authorization/cel"
3636
authorizationmetrics "k8s.io/apiserver/pkg/authorization/metrics"
3737
"k8s.io/apiserver/pkg/authorization/union"
3838
"k8s.io/apiserver/pkg/server/options/authorizationconfig/metrics"
@@ -61,6 +61,7 @@ type reloadableAuthorizerResolver struct {
6161
nodeAuthorizer *node.NodeAuthorizer
6262
rbacAuthorizer *rbac.RBACAuthorizer
6363
abacAuthorizer abac.PolicyList
64+
compiler authorizationcel.Compiler // non-nil and shared across reloads.
6465

6566
lastLoadedLock sync.Mutex
6667
lastLoadedConfig *authzconfig.AuthorizationConfiguration
@@ -148,7 +149,8 @@ func (r *reloadableAuthorizerResolver) newForConfig(authzConfig *authzconfig.Aut
148149
decisionOnError,
149150
configuredAuthorizer.Webhook.MatchConditions,
150151
configuredAuthorizer.Name,
151-
kubeapiserverWebhookMetrics{WebhookMetrics: webhookmetrics.NewWebhookMetrics(), MatcherMetrics: cel.NewMatcherMetrics()},
152+
kubeapiserverWebhookMetrics{WebhookMetrics: webhookmetrics.NewWebhookMetrics(), MatcherMetrics: authorizationcel.NewMatcherMetrics()},
153+
r.compiler,
152154
)
153155
if err != nil {
154156
return nil, nil, err
@@ -175,7 +177,7 @@ type kubeapiserverWebhookMetrics struct {
175177
// kube-apiserver does report webhook metrics
176178
webhookmetrics.WebhookMetrics
177179
// kube-apiserver does report matchCondition metrics
178-
cel.MatcherMetrics
180+
authorizationcel.MatcherMetrics
179181
}
180182

181183
// runReload starts checking the config file for changes and reloads the authorizer when it changes.
@@ -214,7 +216,7 @@ func (r *reloadableAuthorizerResolver) checkFile(ctx context.Context) {
214216
klog.InfoS("found new authorization config data")
215217
r.lastReadData = data
216218

217-
config, err := LoadAndValidateData(data, r.requireNonWebhookTypes)
219+
config, err := LoadAndValidateData(data, r.compiler, r.requireNonWebhookTypes)
218220
if err != nil {
219221
klog.ErrorS(err, "reloading authorization config")
220222
metrics.RecordAuthorizationConfigAutomaticReloadFailure(r.apiServerID)

pkg/kubeapiserver/options/authentication.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"k8s.io/apiserver/pkg/apis/apiserver/install"
4040
apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
4141
"k8s.io/apiserver/pkg/authentication/authenticator"
42+
authenticationcel "k8s.io/apiserver/pkg/authentication/cel"
4243
genericfeatures "k8s.io/apiserver/pkg/features"
4344
genericapiserver "k8s.io/apiserver/pkg/server"
4445
"k8s.io/apiserver/pkg/server/egressselector"
@@ -574,7 +575,7 @@ func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat
574575
}
575576
}
576577

577-
if err := apiservervalidation.ValidateAuthenticationConfiguration(ret.AuthenticationConfig, ret.ServiceAccountIssuers).ToAggregate(); err != nil {
578+
if err := apiservervalidation.ValidateAuthenticationConfiguration(authenticationcel.NewDefaultCompiler(), ret.AuthenticationConfig, ret.ServiceAccountIssuers).ToAggregate(); err != nil {
578579
return kubeauthenticator.Config{}, err
579580
}
580581

@@ -756,7 +757,7 @@ func (o *BuiltInAuthenticationOptions) ApplyTo(
756757
return
757758
}
758759

759-
validationErrs := apiservervalidation.ValidateAuthenticationConfiguration(authConfig, authenticatorConfig.ServiceAccountIssuers)
760+
validationErrs := apiservervalidation.ValidateAuthenticationConfiguration(authenticationcel.NewDefaultCompiler(), authConfig, authenticatorConfig.ServiceAccountIssuers)
760761
if !reflect.DeepEqual(originalFileAnonymousConfig, authConfig.Anonymous) {
761762
validationErrs = append(validationErrs, field.Forbidden(field.NewPath("anonymous"), "changed from initial configuration file"))
762763
}

pkg/kubeapiserver/options/authorization.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"strings"
2222
"time"
2323

24+
authorizationcel "k8s.io/apiserver/pkg/authorization/cel"
2425
genericfeatures "k8s.io/apiserver/pkg/features"
2526
utilfeature "k8s.io/apiserver/pkg/util/feature"
2627

@@ -118,7 +119,7 @@ func (o *BuiltInAuthorizationOptions) Validate() []error {
118119
}
119120

120121
// load/validate kube-apiserver authz config with no opinion about required modes
121-
_, err := authorizer.LoadAndValidateFile(o.AuthorizationConfigurationFile, nil)
122+
_, err := authorizer.LoadAndValidateFile(o.AuthorizationConfigurationFile, authorizationcel.NewDefaultCompiler(), nil)
122123
if err != nil {
123124
return append(allErrors, err)
124125
}
@@ -236,7 +237,7 @@ func (o *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFac
236237
return nil, fmt.Errorf("--%s can not be specified when --%s or --authorization-webhook-* flags are defined", authorizationConfigFlag, authorizationModeFlag)
237238
}
238239
// load/validate kube-apiserver authz config with no opinion about required modes
239-
authorizationConfiguration, err = authorizer.LoadAndValidateFile(o.AuthorizationConfigurationFile, nil)
240+
authorizationConfiguration, err = authorizer.LoadAndValidateFile(o.AuthorizationConfigurationFile, authorizationcel.NewDefaultCompiler(), nil)
240241
if err != nil {
241242
return nil, err
242243
}

staging/src/k8s.io/apiserver/pkg/apis/apiserver/validation/validation.go

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,13 @@ import (
3838
authenticationcel "k8s.io/apiserver/pkg/authentication/cel"
3939
authorizationcel "k8s.io/apiserver/pkg/authorization/cel"
4040
"k8s.io/apiserver/pkg/cel"
41-
"k8s.io/apiserver/pkg/cel/environment"
4241
"k8s.io/apiserver/pkg/features"
4342
utilfeature "k8s.io/apiserver/pkg/util/feature"
4443
"k8s.io/client-go/util/cert"
4544
)
4645

4746
// ValidateAuthenticationConfiguration validates a given AuthenticationConfiguration.
48-
func ValidateAuthenticationConfiguration(c *api.AuthenticationConfiguration, disallowedIssuers []string) field.ErrorList {
47+
func ValidateAuthenticationConfiguration(compiler authenticationcel.Compiler, c *api.AuthenticationConfiguration, disallowedIssuers []string) field.ErrorList {
4948
root := field.NewPath("jwt")
5049
var allErrs field.ErrorList
5150

@@ -62,7 +61,7 @@ func ValidateAuthenticationConfiguration(c *api.AuthenticationConfiguration, dis
6261
seenDiscoveryURLs := sets.New[string]()
6362
for i, a := range c.JWT {
6463
fldPath := root.Index(i)
65-
_, errs := validateJWTAuthenticator(a, fldPath, sets.New(disallowedIssuers...), utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthenticationConfiguration))
64+
_, errs := validateJWTAuthenticator(compiler, a, fldPath, sets.New(disallowedIssuers...), utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthenticationConfiguration))
6665
allErrs = append(allErrs, errs...)
6766

6867
if seenIssuers.Has(a.Issuer.URL) {
@@ -93,15 +92,13 @@ func ValidateAuthenticationConfiguration(c *api.AuthenticationConfiguration, dis
9392
// CompileAndValidateJWTAuthenticator validates a given JWTAuthenticator and returns a CELMapper with the compiled
9493
// CEL expressions for claim mappings and validation rules.
9594
// This is exported for use in oidc package.
96-
func CompileAndValidateJWTAuthenticator(authenticator api.JWTAuthenticator, disallowedIssuers []string) (authenticationcel.CELMapper, field.ErrorList) {
97-
return validateJWTAuthenticator(authenticator, nil, sets.New(disallowedIssuers...), utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthenticationConfiguration))
95+
func CompileAndValidateJWTAuthenticator(compiler authenticationcel.Compiler, authenticator api.JWTAuthenticator, disallowedIssuers []string) (authenticationcel.CELMapper, field.ErrorList) {
96+
return validateJWTAuthenticator(compiler, authenticator, nil, sets.New(disallowedIssuers...), utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthenticationConfiguration))
9897
}
9998

100-
func validateJWTAuthenticator(authenticator api.JWTAuthenticator, fldPath *field.Path, disallowedIssuers sets.Set[string], structuredAuthnFeatureEnabled bool) (authenticationcel.CELMapper, field.ErrorList) {
99+
func validateJWTAuthenticator(compiler authenticationcel.Compiler, authenticator api.JWTAuthenticator, fldPath *field.Path, disallowedIssuers sets.Set[string], structuredAuthnFeatureEnabled bool) (authenticationcel.CELMapper, field.ErrorList) {
101100
var allErrs field.ErrorList
102101

103-
// strictCost is set to true which enables the strict cost for CEL validation.
104-
compiler := authenticationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
105102
state := &validationState{}
106103

107104
allErrs = append(allErrs, validateIssuer(authenticator.Issuer, disallowedIssuers, fldPath.Child("issuer"), structuredAuthnFeatureEnabled)...)
@@ -616,7 +613,7 @@ func compileUserCELExpression(compiler authenticationcel.Compiler, expression au
616613
}
617614

618615
// ValidateAuthorizationConfiguration validates a given AuthorizationConfiguration.
619-
func ValidateAuthorizationConfiguration(fldPath *field.Path, c *api.AuthorizationConfiguration, knownTypes sets.String, repeatableTypes sets.String) field.ErrorList {
616+
func ValidateAuthorizationConfiguration(compiler authorizationcel.Compiler, fldPath *field.Path, c *api.AuthorizationConfiguration, knownTypes sets.Set[string], repeatableTypes sets.Set[string]) field.ErrorList {
620617
allErrs := field.ErrorList{}
621618

622619
if len(c.Authorizers) == 0 {
@@ -633,7 +630,7 @@ func ValidateAuthorizationConfiguration(fldPath *field.Path, c *api.Authorizatio
633630
continue
634631
}
635632
if !knownTypes.Has(aType) {
636-
allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), aType, knownTypes.List()))
633+
allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), aType, sets.List(knownTypes)))
637634
continue
638635
}
639636
if seenAuthorizerTypes.Has(aType) && !repeatableTypes.Has(aType) {
@@ -657,7 +654,7 @@ func ValidateAuthorizationConfiguration(fldPath *field.Path, c *api.Authorizatio
657654
allErrs = append(allErrs, field.Required(fldPath.Child("webhook"), "required when type=Webhook"))
658655
continue
659656
}
660-
allErrs = append(allErrs, ValidateWebhookConfiguration(fldPath, a.Webhook)...)
657+
allErrs = append(allErrs, ValidateWebhookConfiguration(compiler, fldPath, a.Webhook)...)
661658
default:
662659
if a.Webhook != nil {
663660
allErrs = append(allErrs, field.Invalid(fldPath.Child("webhook"), "non-null", "may only be specified when type=Webhook"))
@@ -668,7 +665,7 @@ func ValidateAuthorizationConfiguration(fldPath *field.Path, c *api.Authorizatio
668665
return allErrs
669666
}
670667

671-
func ValidateWebhookConfiguration(fldPath *field.Path, c *api.WebhookConfiguration) field.ErrorList {
668+
func ValidateWebhookConfiguration(compiler authorizationcel.Compiler, fldPath *field.Path, c *api.WebhookConfiguration) field.ErrorList {
672669
allErrs := field.ErrorList{}
673670

674671
if c.Timeout.Duration == 0 {
@@ -740,19 +737,19 @@ func ValidateWebhookConfiguration(fldPath *field.Path, c *api.WebhookConfigurati
740737
allErrs = append(allErrs, field.NotSupported(fldPath.Child("connectionInfo", "type"), c.ConnectionInfo, []string{api.AuthorizationWebhookConnectionInfoTypeInCluster, api.AuthorizationWebhookConnectionInfoTypeKubeConfigFile}))
741738
}
742739

743-
_, errs := compileMatchConditions(c.MatchConditions, fldPath, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthorizationConfiguration))
740+
_, errs := compileMatchConditions(compiler, c.MatchConditions, fldPath, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthorizationConfiguration))
744741
allErrs = append(allErrs, errs...)
745742

746743
return allErrs
747744
}
748745

749746
// ValidateAndCompileMatchConditions validates a given webhook's matchConditions.
750747
// This is exported for use in authz package.
751-
func ValidateAndCompileMatchConditions(matchConditions []api.WebhookMatchCondition) (*authorizationcel.CELMatcher, field.ErrorList) {
752-
return compileMatchConditions(matchConditions, nil, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthorizationConfiguration))
748+
func ValidateAndCompileMatchConditions(compiler authorizationcel.Compiler, matchConditions []api.WebhookMatchCondition) (*authorizationcel.CELMatcher, field.ErrorList) {
749+
return compileMatchConditions(compiler, matchConditions, nil, utilfeature.DefaultFeatureGate.Enabled(features.StructuredAuthorizationConfiguration))
753750
}
754751

755-
func compileMatchConditions(matchConditions []api.WebhookMatchCondition, fldPath *field.Path, structuredAuthzFeatureEnabled bool) (*authorizationcel.CELMatcher, field.ErrorList) {
752+
func compileMatchConditions(compiler authorizationcel.Compiler, matchConditions []api.WebhookMatchCondition, fldPath *field.Path, structuredAuthzFeatureEnabled bool) (*authorizationcel.CELMatcher, field.ErrorList) {
756753
var allErrs field.ErrorList
757754
// should fail when match conditions are used without feature enabled
758755
if len(matchConditions) > 0 && !structuredAuthzFeatureEnabled {
@@ -763,8 +760,6 @@ func compileMatchConditions(matchConditions []api.WebhookMatchCondition, fldPath
763760
return nil, allErrs
764761
}
765762

766-
// strictCost is set to true which enables the strict cost for CEL validation.
767-
compiler := authorizationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
768763
seenExpressions := sets.NewString()
769764
var compilationResults []authorizationcel.CompilationResult
770765
var usesFieldSelector, usesLabelSelector bool

0 commit comments

Comments
 (0)