k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/validating/plugin.go (about) 1 /* 2 Copyright 2024 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package validating 18 19 import ( 20 "context" 21 "io" 22 "sync" 23 24 v1 "k8s.io/api/admissionregistration/v1" 25 "k8s.io/apimachinery/pkg/api/meta" 26 "k8s.io/apiserver/pkg/admission" 27 "k8s.io/apiserver/pkg/admission/initializer" 28 "k8s.io/apiserver/pkg/admission/plugin/cel" 29 "k8s.io/apiserver/pkg/admission/plugin/policy/generic" 30 "k8s.io/apiserver/pkg/admission/plugin/policy/matching" 31 "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" 32 "k8s.io/apiserver/pkg/authorization/authorizer" 33 "k8s.io/apiserver/pkg/cel/environment" 34 "k8s.io/apiserver/pkg/features" 35 utilfeature "k8s.io/apiserver/pkg/util/feature" 36 "k8s.io/client-go/dynamic" 37 "k8s.io/client-go/informers" 38 "k8s.io/client-go/kubernetes" 39 "k8s.io/component-base/featuregate" 40 ) 41 42 const ( 43 // PluginName indicates the name of admission plug-in 44 PluginName = "ValidatingAdmissionPolicy" 45 ) 46 47 var ( 48 lazyCompositionEnvTemplateWithStrictCostInit sync.Once 49 lazyCompositionEnvTemplateWithStrictCost *cel.CompositionEnv 50 51 lazyCompositionEnvTemplateWithoutStrictCostInit sync.Once 52 lazyCompositionEnvTemplateWithoutStrictCost *cel.CompositionEnv 53 ) 54 55 func getCompositionEnvTemplateWithStrictCost() *cel.CompositionEnv { 56 lazyCompositionEnvTemplateWithStrictCostInit.Do(func() { 57 env, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) 58 if err != nil { 59 panic(err) 60 } 61 lazyCompositionEnvTemplateWithStrictCost = env 62 }) 63 return lazyCompositionEnvTemplateWithStrictCost 64 } 65 66 func getCompositionEnvTemplateWithoutStrictCost() *cel.CompositionEnv { 67 lazyCompositionEnvTemplateWithoutStrictCostInit.Do(func() { 68 env, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false)) 69 if err != nil { 70 panic(err) 71 } 72 lazyCompositionEnvTemplateWithoutStrictCost = env 73 }) 74 return lazyCompositionEnvTemplateWithoutStrictCost 75 } 76 77 // Register registers a plugin 78 func Register(plugins *admission.Plugins) { 79 plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) { 80 return NewPlugin(configFile), nil 81 }) 82 } 83 84 // Plugin is an implementation of admission.Interface. 85 type Policy = v1.ValidatingAdmissionPolicy 86 type PolicyBinding = v1.ValidatingAdmissionPolicyBinding 87 type PolicyEvaluator = Validator 88 type PolicyHook = generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator] 89 90 type Plugin struct { 91 *generic.Plugin[PolicyHook] 92 } 93 94 var _ admission.Interface = &Plugin{} 95 var _ admission.ValidationInterface = &Plugin{} 96 var _ initializer.WantsFeatures = &Plugin{} 97 var _ initializer.WantsExcludedAdmissionResources = &Plugin{} 98 99 func NewPlugin(_ io.Reader) *Plugin { 100 handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update) 101 102 return &Plugin{ 103 Plugin: generic.NewPlugin( 104 handler, 105 func(f informers.SharedInformerFactory, client kubernetes.Interface, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) generic.Source[PolicyHook] { 106 return generic.NewPolicySource( 107 f.Admissionregistration().V1().ValidatingAdmissionPolicies().Informer(), 108 f.Admissionregistration().V1().ValidatingAdmissionPolicyBindings().Informer(), 109 NewValidatingAdmissionPolicyAccessor, 110 NewValidatingAdmissionPolicyBindingAccessor, 111 compilePolicy, 112 f, 113 dynamicClient, 114 restMapper, 115 ) 116 }, 117 func(a authorizer.Authorizer, m *matching.Matcher) generic.Dispatcher[PolicyHook] { 118 return NewDispatcher(a, generic.NewPolicyMatcher(m)) 119 }, 120 ), 121 } 122 } 123 124 // Validate makes an admission decision based on the request attributes. 125 func (a *Plugin) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error { 126 return a.Plugin.Dispatch(ctx, attr, o) 127 } 128 129 func (a *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) { 130 a.Plugin.SetEnabled(featureGates.Enabled(features.ValidatingAdmissionPolicy)) 131 } 132 133 func compilePolicy(policy *Policy) Validator { 134 hasParam := false 135 if policy.Spec.ParamKind != nil { 136 hasParam = true 137 } 138 strictCost := utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP) 139 optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true, StrictCost: strictCost} 140 expressionOptionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false, StrictCost: strictCost} 141 failurePolicy := policy.Spec.FailurePolicy 142 var matcher matchconditions.Matcher = nil 143 matchConditions := policy.Spec.MatchConditions 144 var compositionEnvTemplate *cel.CompositionEnv 145 if strictCost { 146 compositionEnvTemplate = getCompositionEnvTemplateWithStrictCost() 147 } else { 148 compositionEnvTemplate = getCompositionEnvTemplateWithoutStrictCost() 149 } 150 filterCompiler := cel.NewCompositedCompilerFromTemplate(compositionEnvTemplate) 151 filterCompiler.CompileAndStoreVariables(convertv1beta1Variables(policy.Spec.Variables), optionalVars, environment.StoredExpressions) 152 153 if len(matchConditions) > 0 { 154 matchExpressionAccessors := make([]cel.ExpressionAccessor, len(matchConditions)) 155 for i := range matchConditions { 156 matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i]) 157 } 158 matcher = matchconditions.NewMatcher(filterCompiler.Compile(matchExpressionAccessors, optionalVars, environment.StoredExpressions), failurePolicy, "policy", "validate", policy.Name) 159 } 160 res := NewValidator( 161 filterCompiler.Compile(convertv1Validations(policy.Spec.Validations), optionalVars, environment.StoredExpressions), 162 matcher, 163 filterCompiler.Compile(convertv1AuditAnnotations(policy.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions), 164 filterCompiler.Compile(convertv1MessageExpressions(policy.Spec.Validations), expressionOptionalVars, environment.StoredExpressions), 165 failurePolicy, 166 ) 167 168 return res 169 } 170 171 func convertv1Validations(inputValidations []v1.Validation) []cel.ExpressionAccessor { 172 celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations)) 173 for i, validation := range inputValidations { 174 validation := ValidationCondition{ 175 Expression: validation.Expression, 176 Message: validation.Message, 177 Reason: validation.Reason, 178 } 179 celExpressionAccessor[i] = &validation 180 } 181 return celExpressionAccessor 182 } 183 184 func convertv1MessageExpressions(inputValidations []v1.Validation) []cel.ExpressionAccessor { 185 celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations)) 186 for i, validation := range inputValidations { 187 if validation.MessageExpression != "" { 188 condition := MessageExpressionCondition{ 189 MessageExpression: validation.MessageExpression, 190 } 191 celExpressionAccessor[i] = &condition 192 } 193 } 194 return celExpressionAccessor 195 } 196 197 func convertv1AuditAnnotations(inputValidations []v1.AuditAnnotation) []cel.ExpressionAccessor { 198 celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations)) 199 for i, validation := range inputValidations { 200 validation := AuditAnnotationCondition{ 201 Key: validation.Key, 202 ValueExpression: validation.ValueExpression, 203 } 204 celExpressionAccessor[i] = &validation 205 } 206 return celExpressionAccessor 207 } 208 209 func convertv1beta1Variables(variables []v1.Variable) []cel.NamedExpressionAccessor { 210 namedExpressions := make([]cel.NamedExpressionAccessor, len(variables)) 211 for i, variable := range variables { 212 namedExpressions[i] = &Variable{Name: variable.Name, Expression: variable.Expression} 213 } 214 return namedExpressions 215 }