mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
rebase: update kubernetes to 1.28.0 in main
updating kubernetes to 1.28.0 in the main repo. Signed-off-by: Madhu Rajanna <madhupr007@gmail.com>
This commit is contained in:
committed by
mergify[bot]
parent
b2fdc269c3
commit
ff3e84ad67
10
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/admission.go
generated
vendored
@ -24,7 +24,6 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/component-base/featuregate"
|
||||
@ -74,7 +73,6 @@ type celAdmissionPlugin struct {
|
||||
dynamicClient dynamic.Interface
|
||||
stopCh <-chan struct{}
|
||||
authorizer authorizer.Authorizer
|
||||
schemaResolver resolver.SchemaResolver
|
||||
}
|
||||
|
||||
var _ initializer.WantsExternalKubeInformerFactory = &celAdmissionPlugin{}
|
||||
@ -83,7 +81,6 @@ var _ initializer.WantsRESTMapper = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsDynamicClient = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsDrainedNotification = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsAuthorizer = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsSchemaResolver = &celAdmissionPlugin{}
|
||||
var _ admission.InitializationValidator = &celAdmissionPlugin{}
|
||||
var _ admission.ValidationInterface = &celAdmissionPlugin{}
|
||||
|
||||
@ -116,11 +113,6 @@ func (c *celAdmissionPlugin) SetDrainedNotification(stopCh <-chan struct{}) {
|
||||
func (c *celAdmissionPlugin) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
c.authorizer = authorizer
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) SetSchemaResolver(resolver resolver.SchemaResolver) {
|
||||
c.schemaResolver = resolver
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
||||
if featureGates.Enabled(features.ValidatingAdmissionPolicy) {
|
||||
c.enabled = true
|
||||
@ -154,7 +146,7 @@ func (c *celAdmissionPlugin) ValidateInitialization() error {
|
||||
if c.authorizer == nil {
|
||||
return errors.New("missing authorizer")
|
||||
}
|
||||
c.evaluator = NewAdmissionController(c.informerFactory, c.client, c.restMapper, c.schemaResolver /* (optional) */, c.dynamicClient, c.authorizer)
|
||||
c.evaluator = NewAdmissionController(c.informerFactory, c.client, c.restMapper, c.dynamicClient, c.authorizer)
|
||||
if err := c.evaluator.ValidateInitialization(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
133
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/caching_authorizer.go
generated
vendored
Normal file
133
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/caching_authorizer.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
type authzResult struct {
|
||||
authorized authorizer.Decision
|
||||
reason string
|
||||
err error
|
||||
}
|
||||
|
||||
type cachingAuthorizer struct {
|
||||
authorizer authorizer.Authorizer
|
||||
decisions map[string]authzResult
|
||||
}
|
||||
|
||||
func newCachingAuthorizer(in authorizer.Authorizer) authorizer.Authorizer {
|
||||
return &cachingAuthorizer{
|
||||
authorizer: in,
|
||||
decisions: make(map[string]authzResult),
|
||||
}
|
||||
}
|
||||
|
||||
// The attribute accessors known to cache key construction. If this fails to compile, the cache
|
||||
// implementation may need to be updated.
|
||||
var _ authorizer.Attributes = (interface {
|
||||
GetUser() user.Info
|
||||
GetVerb() string
|
||||
IsReadOnly() bool
|
||||
GetNamespace() string
|
||||
GetResource() string
|
||||
GetSubresource() string
|
||||
GetName() string
|
||||
GetAPIGroup() string
|
||||
GetAPIVersion() string
|
||||
IsResourceRequest() bool
|
||||
GetPath() string
|
||||
})(nil)
|
||||
|
||||
// The user info accessors known to cache key construction. If this fails to compile, the cache
|
||||
// implementation may need to be updated.
|
||||
var _ user.Info = (interface {
|
||||
GetName() string
|
||||
GetUID() string
|
||||
GetGroups() []string
|
||||
GetExtra() map[string][]string
|
||||
})(nil)
|
||||
|
||||
// Authorize returns an authorization decision by delegating to another Authorizer. If an equivalent
|
||||
// check has already been performed, a cached result is returned. Not safe for concurrent use.
|
||||
func (ca *cachingAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
serializableAttributes := authorizer.AttributesRecord{
|
||||
Verb: a.GetVerb(),
|
||||
Namespace: a.GetNamespace(),
|
||||
APIGroup: a.GetAPIGroup(),
|
||||
APIVersion: a.GetAPIVersion(),
|
||||
Resource: a.GetResource(),
|
||||
Subresource: a.GetSubresource(),
|
||||
Name: a.GetName(),
|
||||
ResourceRequest: a.IsResourceRequest(),
|
||||
Path: a.GetPath(),
|
||||
}
|
||||
|
||||
if u := a.GetUser(); u != nil {
|
||||
di := &user.DefaultInfo{
|
||||
Name: u.GetName(),
|
||||
UID: u.GetUID(),
|
||||
}
|
||||
|
||||
// Differently-ordered groups or extras could cause otherwise-equivalent checks to
|
||||
// have distinct cache keys.
|
||||
if groups := u.GetGroups(); len(groups) > 0 {
|
||||
di.Groups = make([]string, len(groups))
|
||||
copy(di.Groups, groups)
|
||||
sort.Strings(di.Groups)
|
||||
}
|
||||
|
||||
if extra := u.GetExtra(); len(extra) > 0 {
|
||||
di.Extra = make(map[string][]string, len(extra))
|
||||
for k, vs := range extra {
|
||||
vdupe := make([]string, len(vs))
|
||||
copy(vdupe, vs)
|
||||
sort.Strings(vdupe)
|
||||
di.Extra[k] = vdupe
|
||||
}
|
||||
}
|
||||
|
||||
serializableAttributes.User = di
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
if err := json.NewEncoder(&b).Encode(serializableAttributes); err != nil {
|
||||
return authorizer.DecisionNoOpinion, "", err
|
||||
}
|
||||
key := b.String()
|
||||
|
||||
if cached, ok := ca.decisions[key]; ok {
|
||||
return cached.authorized, cached.reason, cached.err
|
||||
}
|
||||
|
||||
authorized, reason, err := ca.authorizer.Authorize(ctx, a)
|
||||
|
||||
ca.decisions[key] = authzResult{
|
||||
authorized: authorized,
|
||||
reason: reason,
|
||||
err: err,
|
||||
}
|
||||
|
||||
return authorized, reason, err
|
||||
}
|
447
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go
generated
vendored
447
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/controller.go
generated
vendored
@ -25,30 +25,29 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ CELPolicyEvaluator = &celAdmissionController{}
|
||||
@ -66,22 +65,24 @@ type celAdmissionController struct {
|
||||
// A snapshot of the current policy configuration is synced with this field
|
||||
// asynchronously
|
||||
definitions atomic.Value
|
||||
|
||||
authz authorizer.Authorizer
|
||||
}
|
||||
|
||||
// Everything someone might need to validate a single ValidatingPolicyDefinition
|
||||
// against all of its registered bindings.
|
||||
type policyData struct {
|
||||
definitionInfo
|
||||
paramController generic.Controller[runtime.Object]
|
||||
bindings []bindingInfo
|
||||
paramInfo
|
||||
bindings []bindingInfo
|
||||
}
|
||||
|
||||
// contains the cel PolicyDecisions along with the ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding
|
||||
// that determined the decision
|
||||
type policyDecisionWithMetadata struct {
|
||||
PolicyDecision
|
||||
Definition *v1alpha1.ValidatingAdmissionPolicy
|
||||
Binding *v1alpha1.ValidatingAdmissionPolicyBinding
|
||||
Definition *v1beta1.ValidatingAdmissionPolicy
|
||||
Binding *v1beta1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
// namespaceName is used as a key in definitionInfo and bindingInfos
|
||||
@ -97,7 +98,7 @@ type definitionInfo struct {
|
||||
|
||||
// Last value seen by this controller to be used in policy enforcement
|
||||
// May not be nil
|
||||
lastReconciledValue *v1alpha1.ValidatingAdmissionPolicy
|
||||
lastReconciledValue *v1beta1.ValidatingAdmissionPolicy
|
||||
}
|
||||
|
||||
type bindingInfo struct {
|
||||
@ -106,7 +107,7 @@ type bindingInfo struct {
|
||||
|
||||
// Last value seen by this controller to be used in policy enforcement
|
||||
// May not be nil
|
||||
lastReconciledValue *v1alpha1.ValidatingAdmissionPolicyBinding
|
||||
lastReconciledValue *v1beta1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
type paramInfo struct {
|
||||
@ -116,6 +117,9 @@ type paramInfo struct {
|
||||
// Function to call to stop the informer and clean up the controller
|
||||
stop func()
|
||||
|
||||
// Whether this param is cluster or namespace scoped
|
||||
scope meta.RESTScope
|
||||
|
||||
// Policy Definitions which refer to this param CRD
|
||||
dependentDefinitions sets.Set[namespacedName]
|
||||
}
|
||||
@ -125,29 +129,24 @@ func NewAdmissionController(
|
||||
informerFactory informers.SharedInformerFactory,
|
||||
client kubernetes.Interface,
|
||||
restMapper meta.RESTMapper,
|
||||
schemaResolver resolver.SchemaResolver,
|
||||
dynamicClient dynamic.Interface,
|
||||
authz authorizer.Authorizer,
|
||||
) CELPolicyEvaluator {
|
||||
var typeChecker *TypeChecker
|
||||
if schemaResolver != nil {
|
||||
typeChecker = &TypeChecker{schemaResolver: schemaResolver, restMapper: restMapper}
|
||||
}
|
||||
return &celAdmissionController{
|
||||
definitions: atomic.Value{},
|
||||
policyController: newPolicyController(
|
||||
restMapper,
|
||||
client,
|
||||
dynamicClient,
|
||||
typeChecker,
|
||||
cel.NewFilterCompiler(),
|
||||
informerFactory,
|
||||
nil,
|
||||
NewMatcher(matching.NewMatcher(informerFactory.Core().V1().Namespaces().Lister(), client)),
|
||||
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicy](
|
||||
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies().Informer()),
|
||||
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicyBinding](
|
||||
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicyBindings().Informer()),
|
||||
authz,
|
||||
generic.NewInformer[*v1beta1.ValidatingAdmissionPolicy](
|
||||
informerFactory.Admissionregistration().V1beta1().ValidatingAdmissionPolicies().Informer()),
|
||||
generic.NewInformer[*v1beta1.ValidatingAdmissionPolicyBinding](
|
||||
informerFactory.Admissionregistration().V1beta1().ValidatingAdmissionPolicyBindings().Informer()),
|
||||
),
|
||||
authz: authz,
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,21 +192,21 @@ func (c *celAdmissionController) Validate(
|
||||
|
||||
var deniedDecisions []policyDecisionWithMetadata
|
||||
|
||||
addConfigError := func(err error, definition *v1alpha1.ValidatingAdmissionPolicy, binding *v1alpha1.ValidatingAdmissionPolicyBinding) {
|
||||
addConfigError := func(err error, definition *v1beta1.ValidatingAdmissionPolicy, binding *v1beta1.ValidatingAdmissionPolicyBinding) {
|
||||
// we always default the FailurePolicy if it is unset and validate it in API level
|
||||
var policy v1alpha1.FailurePolicyType
|
||||
var policy v1beta1.FailurePolicyType
|
||||
if definition.Spec.FailurePolicy == nil {
|
||||
policy = v1alpha1.Fail
|
||||
policy = v1beta1.Fail
|
||||
} else {
|
||||
policy = *definition.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
// apply FailurePolicy specified in ValidatingAdmissionPolicy, the default would be Fail
|
||||
switch policy {
|
||||
case v1alpha1.Ignore:
|
||||
case v1beta1.Ignore:
|
||||
// TODO: add metrics for ignored error here
|
||||
return
|
||||
case v1alpha1.Fail:
|
||||
case v1beta1.Fail:
|
||||
var message string
|
||||
if binding == nil {
|
||||
message = fmt.Errorf("failed to configure policy: %w", err).Error()
|
||||
@ -235,9 +234,17 @@ func (c *celAdmissionController) Validate(
|
||||
}
|
||||
policyDatas := c.definitions.Load().([]policyData)
|
||||
|
||||
authz := newCachingAuthorizer(c.authz)
|
||||
|
||||
for _, definitionInfo := range policyDatas {
|
||||
// versionedAttributes will be set to non-nil inside of the loop, but
|
||||
// is scoped outside of the param loop so we only convert once. We defer
|
||||
// conversion so that it is only performed when we know a policy matches,
|
||||
// saving the cost of converting non-matching requests.
|
||||
var versionedAttr *admission.VersionedAttributes
|
||||
|
||||
definition := definitionInfo.lastReconciledValue
|
||||
matches, matchKind, err := c.policyController.matcher.DefinitionMatches(a, o, definition)
|
||||
matches, matchResource, matchKind, err := c.policyController.matcher.DefinitionMatches(a, o, definition)
|
||||
if err != nil {
|
||||
// Configuration error.
|
||||
addConfigError(err, definition, nil)
|
||||
@ -267,65 +274,13 @@ func (c *celAdmissionController) Validate(
|
||||
continue
|
||||
}
|
||||
|
||||
var param runtime.Object
|
||||
|
||||
// versionedAttributes will be set to non-nil inside of the loop, but
|
||||
// is scoped outside of the param loop so we only convert once. We defer
|
||||
// conversion so that it is only performed when we know a policy matches,
|
||||
// saving the cost of converting non-matching requests.
|
||||
var versionedAttr *admission.VersionedAttributes
|
||||
|
||||
// If definition has paramKind, paramRef is required in binding.
|
||||
// If definition has no paramKind, paramRef set in binding will be ignored.
|
||||
paramKind := definition.Spec.ParamKind
|
||||
paramRef := binding.Spec.ParamRef
|
||||
if paramKind != nil && paramRef != nil {
|
||||
paramController := definitionInfo.paramController
|
||||
if paramController == nil {
|
||||
addConfigError(fmt.Errorf("paramKind kind `%v` not known",
|
||||
paramKind.String()), definition, binding)
|
||||
continue
|
||||
}
|
||||
|
||||
// If the param informer for this admission policy has not yet
|
||||
// had time to perform an initial listing, don't attempt to use
|
||||
// it.
|
||||
timeoutCtx, cancel := context.WithTimeout(c.policyController.context, 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if !cache.WaitForCacheSync(timeoutCtx.Done(), paramController.HasSynced) {
|
||||
addConfigError(fmt.Errorf("paramKind kind `%v` not yet synced to use for admission",
|
||||
paramKind.String()), definition, binding)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(paramRef.Namespace) == 0 {
|
||||
param, err = paramController.Informer().Get(paramRef.Name)
|
||||
} else {
|
||||
param, err = paramController.Informer().Namespaced(paramRef.Namespace).Get(paramRef.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Apply failure policy
|
||||
addConfigError(err, definition, binding)
|
||||
|
||||
if k8serrors.IsInvalid(err) {
|
||||
// Param mis-configured
|
||||
// require to set paramRef.namespace for namespaced resource and unset paramRef.namespace for cluster scoped resource
|
||||
continue
|
||||
} else if k8serrors.IsNotFound(err) {
|
||||
// Param not yet available. User may need to wait a bit
|
||||
// before being able to use it for validation.
|
||||
continue
|
||||
}
|
||||
|
||||
// There was a bad internal error
|
||||
utilruntime.HandleError(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if versionedAttr == nil {
|
||||
params, err := c.collectParams(definition.Spec.ParamKind, definitionInfo.paramInfo, binding.Spec.ParamRef, a.GetNamespace())
|
||||
if err != nil {
|
||||
addConfigError(err, definition, binding)
|
||||
continue
|
||||
} else if versionedAttr == nil && len(params) > 0 {
|
||||
// As optimization versionedAttr creation is deferred until
|
||||
// first use. Since > 0 params, we will validate
|
||||
va, err := admission.NewVersionedAttributes(a, matchKind, o)
|
||||
if err != nil {
|
||||
wrappedErr := fmt.Errorf("failed to convert object version: %w", err)
|
||||
@ -335,68 +290,98 @@ func (c *celAdmissionController) Validate(
|
||||
versionedAttr = va
|
||||
}
|
||||
|
||||
validationResult := bindingInfo.validator.Validate(ctx, versionedAttr, param, celconfig.RuntimeCELCostBudget)
|
||||
if err != nil {
|
||||
// runtime error. Apply failure policy
|
||||
wrappedError := fmt.Errorf("failed to evaluate CEL expression: %w", err)
|
||||
addConfigError(wrappedError, definition, binding)
|
||||
continue
|
||||
var validationResults []ValidateResult
|
||||
var namespace *v1.Namespace
|
||||
namespaceName := a.GetNamespace()
|
||||
|
||||
// Special case, the namespace object has the namespace of itself (maybe a bug).
|
||||
// unset it if the incoming object is a namespace
|
||||
if gvk := a.GetKind(); gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
|
||||
namespaceName = ""
|
||||
}
|
||||
|
||||
for i, decision := range validationResult.Decisions {
|
||||
switch decision.Action {
|
||||
case ActionAdmit:
|
||||
if decision.Evaluation == EvalError {
|
||||
celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||
}
|
||||
case ActionDeny:
|
||||
for _, action := range binding.Spec.ValidationActions {
|
||||
switch action {
|
||||
case v1alpha1.Deny:
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
PolicyDecision: decision,
|
||||
})
|
||||
celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||
case v1alpha1.Audit:
|
||||
c.publishValidationFailureAnnotation(binding, i, decision, versionedAttr)
|
||||
celmetrics.Metrics.ObserveAudit(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||
case v1alpha1.Warn:
|
||||
warning.AddWarning(ctx, "", fmt.Sprintf("Validation failed for ValidatingAdmissionPolicy '%s' with binding '%s': %s", definition.Name, binding.Name, decision.Message))
|
||||
celmetrics.Metrics.ObserveWarn(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'",
|
||||
decision.Action, binding.Name, definition.Name)
|
||||
// if it is cluster scoped, namespaceName will be empty
|
||||
// Otherwise, get the Namespace resource.
|
||||
if namespaceName != "" {
|
||||
namespace, err = c.policyController.matcher.GetNamespace(namespaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, auditAnnotation := range validationResult.AuditAnnotations {
|
||||
switch auditAnnotation.Action {
|
||||
case AuditAnnotationActionPublish:
|
||||
value := auditAnnotation.Value
|
||||
if len(auditAnnotation.Value) > maxAuditAnnotationValueLength {
|
||||
value = value[:maxAuditAnnotationValueLength]
|
||||
}
|
||||
auditAnnotationCollector.add(auditAnnotation.Key, value)
|
||||
case AuditAnnotationActionError:
|
||||
// When failurePolicy=fail, audit annotation errors result in deny
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
PolicyDecision: PolicyDecision{
|
||||
Action: ActionDeny,
|
||||
Evaluation: EvalError,
|
||||
Message: auditAnnotation.Error,
|
||||
Elapsed: auditAnnotation.Elapsed,
|
||||
for _, param := range params {
|
||||
var p runtime.Object = param
|
||||
if p != nil && p.GetObjectKind().GroupVersionKind().Empty() {
|
||||
// Make sure param has TypeMeta populated
|
||||
// This is a simple hack to make sure typeMeta is
|
||||
// available to CEL without making copies of objects, etc.
|
||||
p = &wrappedParam{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: definition.Spec.ParamKind.APIVersion,
|
||||
Kind: definition.Spec.ParamKind.Kind,
|
||||
},
|
||||
})
|
||||
celmetrics.Metrics.ObserveRejection(ctx, auditAnnotation.Elapsed, definition.Name, binding.Name, "active")
|
||||
case AuditAnnotationActionExclude: // skip it
|
||||
default:
|
||||
return fmt.Errorf("unsupported AuditAnnotation Action: %s", auditAnnotation.Action)
|
||||
nested: param,
|
||||
}
|
||||
}
|
||||
validationResults = append(validationResults, bindingInfo.validator.Validate(ctx, matchResource, versionedAttr, p, namespace, celconfig.RuntimeCELCostBudget, authz))
|
||||
}
|
||||
|
||||
for _, validationResult := range validationResults {
|
||||
for i, decision := range validationResult.Decisions {
|
||||
switch decision.Action {
|
||||
case ActionAdmit:
|
||||
if decision.Evaluation == EvalError {
|
||||
celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||
}
|
||||
case ActionDeny:
|
||||
for _, action := range binding.Spec.ValidationActions {
|
||||
switch action {
|
||||
case v1beta1.Deny:
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
PolicyDecision: decision,
|
||||
})
|
||||
celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||
case v1beta1.Audit:
|
||||
c.publishValidationFailureAnnotation(binding, i, decision, versionedAttr)
|
||||
celmetrics.Metrics.ObserveAudit(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||
case v1beta1.Warn:
|
||||
warning.AddWarning(ctx, "", fmt.Sprintf("Validation failed for ValidatingAdmissionPolicy '%s' with binding '%s': %s", definition.Name, binding.Name, decision.Message))
|
||||
celmetrics.Metrics.ObserveWarn(ctx, decision.Elapsed, definition.Name, binding.Name, "active")
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'",
|
||||
decision.Action, binding.Name, definition.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, auditAnnotation := range validationResult.AuditAnnotations {
|
||||
switch auditAnnotation.Action {
|
||||
case AuditAnnotationActionPublish:
|
||||
value := auditAnnotation.Value
|
||||
if len(auditAnnotation.Value) > maxAuditAnnotationValueLength {
|
||||
value = value[:maxAuditAnnotationValueLength]
|
||||
}
|
||||
auditAnnotationCollector.add(auditAnnotation.Key, value)
|
||||
case AuditAnnotationActionError:
|
||||
// When failurePolicy=fail, audit annotation errors result in deny
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
PolicyDecision: PolicyDecision{
|
||||
Action: ActionDeny,
|
||||
Evaluation: EvalError,
|
||||
Message: auditAnnotation.Error,
|
||||
Elapsed: auditAnnotation.Elapsed,
|
||||
},
|
||||
})
|
||||
celmetrics.Metrics.ObserveRejection(ctx, auditAnnotation.Elapsed, definition.Name, binding.Name, "active")
|
||||
case AuditAnnotationActionExclude: // skip it
|
||||
default:
|
||||
return fmt.Errorf("unsupported AuditAnnotation Action: %s", auditAnnotation.Action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -425,7 +410,124 @@ func (c *celAdmissionController) Validate(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *celAdmissionController) publishValidationFailureAnnotation(binding *v1alpha1.ValidatingAdmissionPolicyBinding, expressionIndex int, decision PolicyDecision, attributes admission.Attributes) {
|
||||
// Returns objects to use to evaluate the policy
|
||||
func (c *celAdmissionController) collectParams(
|
||||
paramKind *v1beta1.ParamKind,
|
||||
info paramInfo,
|
||||
paramRef *v1beta1.ParamRef,
|
||||
namespace string,
|
||||
) ([]runtime.Object, error) {
|
||||
// If definition has paramKind, paramRef is required in binding.
|
||||
// If definition has no paramKind, paramRef set in binding will be ignored.
|
||||
var params []runtime.Object
|
||||
var paramStore generic.NamespacedLister[runtime.Object]
|
||||
|
||||
// Make sure the param kind is ready to use
|
||||
if paramKind != nil && paramRef != nil {
|
||||
if info.controller == nil {
|
||||
return nil, fmt.Errorf("paramKind kind `%v` not known",
|
||||
paramKind.String())
|
||||
}
|
||||
|
||||
// Set up cluster-scoped, or namespaced access to the params
|
||||
// "default" if not provided, and paramKind is namespaced
|
||||
paramStore = info.controller.Informer()
|
||||
if info.scope.Name() == meta.RESTScopeNameNamespace {
|
||||
paramsNamespace := namespace
|
||||
if len(paramRef.Namespace) > 0 {
|
||||
paramsNamespace = paramRef.Namespace
|
||||
} else if len(paramsNamespace) == 0 {
|
||||
// You must supply namespace if your matcher can possibly
|
||||
// match a cluster-scoped resource
|
||||
return nil, fmt.Errorf("cannot use namespaced paramRef in policy binding that matches cluster-scoped resources")
|
||||
}
|
||||
|
||||
paramStore = info.controller.Informer().Namespaced(paramsNamespace)
|
||||
}
|
||||
|
||||
// If the param informer for this admission policy has not yet
|
||||
// had time to perform an initial listing, don't attempt to use
|
||||
// it.
|
||||
timeoutCtx, cancel := context.WithTimeout(c.policyController.context, 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if !cache.WaitForCacheSync(timeoutCtx.Done(), info.controller.HasSynced) {
|
||||
return nil, fmt.Errorf("paramKind kind `%v` not yet synced to use for admission",
|
||||
paramKind.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Find params to use with policy
|
||||
switch {
|
||||
case paramKind == nil:
|
||||
// ParamKind is unset. Ignore any globalParamRef or namespaceParamRef
|
||||
// setting.
|
||||
return []runtime.Object{nil}, nil
|
||||
case paramRef == nil:
|
||||
// Policy ParamKind is set, but binding does not use it.
|
||||
// Validate with nil params
|
||||
return []runtime.Object{nil}, nil
|
||||
case len(paramRef.Namespace) > 0 && info.scope.Name() == meta.RESTScopeRoot.Name():
|
||||
// Not allowed to set namespace for cluster-scoped param
|
||||
return nil, fmt.Errorf("paramRef.namespace must not be provided for a cluster-scoped `paramKind`")
|
||||
|
||||
case len(paramRef.Name) > 0:
|
||||
if paramRef.Selector != nil {
|
||||
// This should be validated, but just in case.
|
||||
return nil, fmt.Errorf("paramRef.name and paramRef.selector are mutually exclusive")
|
||||
}
|
||||
|
||||
switch param, err := paramStore.Get(paramRef.Name); {
|
||||
case err == nil:
|
||||
params = []runtime.Object{param}
|
||||
case k8serrors.IsNotFound(err):
|
||||
// Param not yet available. User may need to wait a bit
|
||||
// before being able to use it for validation.
|
||||
//
|
||||
// Set params to nil to prepare for not found action
|
||||
params = nil
|
||||
case k8serrors.IsInvalid(err):
|
||||
// Param mis-configured
|
||||
// require to set namespace for namespaced resource
|
||||
// and unset namespace for cluster scoped resource
|
||||
return nil, err
|
||||
default:
|
||||
// Internal error
|
||||
utilruntime.HandleError(err)
|
||||
return nil, err
|
||||
}
|
||||
case paramRef.Selector != nil:
|
||||
// Select everything by default if empty name and selector
|
||||
selector, err := metav1.LabelSelectorAsSelector(paramRef.Selector)
|
||||
if err != nil {
|
||||
// Cannot parse label selector: configuration error
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
paramList, err := paramStore.List(selector)
|
||||
if err != nil {
|
||||
// There was a bad internal error
|
||||
utilruntime.HandleError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Successfully grabbed params
|
||||
params = paramList
|
||||
default:
|
||||
// Should be unreachable due to validation
|
||||
return nil, fmt.Errorf("one of name or selector must be provided")
|
||||
}
|
||||
|
||||
// Apply fail action for params not found case
|
||||
if len(params) == 0 && paramRef.ParameterNotFoundAction != nil && *paramRef.ParameterNotFoundAction == v1beta1.DenyAction {
|
||||
return nil, errors.New("no params found for policy binding with `Deny` parameterNotFoundAction")
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (c *celAdmissionController) publishValidationFailureAnnotation(binding *v1beta1.ValidatingAdmissionPolicyBinding, expressionIndex int, decision PolicyDecision, attributes admission.Attributes) {
|
||||
key := "validation.policy.admission.k8s.io/validation_failure"
|
||||
// Marshal to a list of failures since, in the future, we may need to support multiple failures
|
||||
valueJson, err := utiljson.Marshal([]validationFailureValue{{
|
||||
@ -459,11 +561,11 @@ func (c *celAdmissionController) refreshPolicies() {
|
||||
// validationFailureValue defines the JSON format of a "validation.policy.admission.k8s.io/validation_failure" audit
|
||||
// annotation value.
|
||||
type validationFailureValue struct {
|
||||
Message string `json:"message"`
|
||||
Policy string `json:"policy"`
|
||||
Binding string `json:"binding"`
|
||||
ExpressionIndex int `json:"expressionIndex"`
|
||||
ValidationActions []v1alpha1.ValidationAction `json:"validationActions"`
|
||||
Message string `json:"message"`
|
||||
Policy string `json:"policy"`
|
||||
Binding string `json:"binding"`
|
||||
ExpressionIndex int `json:"expressionIndex"`
|
||||
ValidationActions []v1beta1.ValidationAction `json:"validationActions"`
|
||||
}
|
||||
|
||||
type auditAnnotationCollector struct {
|
||||
@ -500,3 +602,48 @@ func (a auditAnnotationCollector) publish(policyName string, attributes admissio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A workaround to fact that native types do not have TypeMeta populated, which
|
||||
// is needed for CEL expressions to be able to access the value.
|
||||
type wrappedParam struct {
|
||||
metav1.TypeMeta
|
||||
nested runtime.Object
|
||||
}
|
||||
|
||||
func (w *wrappedParam) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("MarshalJSON unimplemented for wrappedParam")
|
||||
}
|
||||
|
||||
func (w *wrappedParam) UnmarshalJSON(data []byte) error {
|
||||
return errors.New("UnmarshalJSON unimplemented for wrappedParam")
|
||||
}
|
||||
|
||||
func (w *wrappedParam) ToUnstructured() interface{} {
|
||||
res, err := runtime.DefaultUnstructuredConverter.ToUnstructured(w.nested)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
metaRes, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&w.TypeMeta)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for k, v := range metaRes {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (w *wrappedParam) DeepCopyObject() runtime.Object {
|
||||
return &wrappedParam{
|
||||
TypeMeta: w.TypeMeta,
|
||||
nested: w.nested.DeepCopyObject(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *wrappedParam) GetObjectKind() schema.ObjectKind {
|
||||
return w
|
||||
}
|
||||
|
@ -23,11 +23,10 @@ import (
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
@ -36,13 +35,11 @@ import (
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
k8sscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
@ -50,36 +47,30 @@ type policyController struct {
|
||||
once sync.Once
|
||||
context context.Context
|
||||
dynamicClient dynamic.Interface
|
||||
informerFactory informers.SharedInformerFactory
|
||||
restMapper meta.RESTMapper
|
||||
policyDefinitionsController generic.Controller[*v1alpha1.ValidatingAdmissionPolicy]
|
||||
policyBindingController generic.Controller[*v1alpha1.ValidatingAdmissionPolicyBinding]
|
||||
policyDefinitionsController generic.Controller[*v1beta1.ValidatingAdmissionPolicy]
|
||||
policyBindingController generic.Controller[*v1beta1.ValidatingAdmissionPolicyBinding]
|
||||
|
||||
// Provided to the policy's Compile function as an injected dependency to
|
||||
// assist with compiling its expressions to CEL
|
||||
// pass nil to create filter compiler in demand
|
||||
filterCompiler cel.FilterCompiler
|
||||
|
||||
matcher Matcher
|
||||
|
||||
newValidator
|
||||
|
||||
// The TypeCheck checks the policy's expressions for type errors.
|
||||
// Type of params is defined in policy.Spec.ParamsKind
|
||||
// Types of object are calculated from policy.Spec.MatchingConstraints
|
||||
typeChecker *TypeChecker
|
||||
|
||||
// Lock which protects:
|
||||
// - cachedPolicies
|
||||
// - paramCRDControllers
|
||||
// - definitionInfo
|
||||
// - bindingInfos
|
||||
// - definitionsToBindings
|
||||
// All other fields should be assumed constant
|
||||
client kubernetes.Interface
|
||||
// Lock which protects
|
||||
// All Below fields
|
||||
// All above fields should be assumed constant
|
||||
mutex sync.RWMutex
|
||||
|
||||
cachedPolicies []policyData
|
||||
|
||||
// controller and metadata
|
||||
paramsCRDControllers map[v1alpha1.ParamKind]*paramInfo
|
||||
paramsCRDControllers map[v1beta1.ParamKind]*paramInfo
|
||||
|
||||
// Index for each definition namespace/name, contains all binding
|
||||
// namespace/names known to exist for that definition
|
||||
@ -94,32 +85,26 @@ type policyController struct {
|
||||
// All keys must have at least one dependent binding
|
||||
// All binding names MUST exist as a key bindingInfos
|
||||
definitionsToBindings map[namespacedName]sets.Set[namespacedName]
|
||||
|
||||
client kubernetes.Interface
|
||||
|
||||
authz authorizer.Authorizer
|
||||
}
|
||||
|
||||
type newValidator func(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failurePolicy *v1.FailurePolicyType, authorizer authorizer.Authorizer) Validator
|
||||
type newValidator func(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failurePolicy *v1.FailurePolicyType) Validator
|
||||
|
||||
func newPolicyController(
|
||||
restMapper meta.RESTMapper,
|
||||
client kubernetes.Interface,
|
||||
dynamicClient dynamic.Interface,
|
||||
typeChecker *TypeChecker,
|
||||
informerFactory informers.SharedInformerFactory,
|
||||
filterCompiler cel.FilterCompiler,
|
||||
matcher Matcher,
|
||||
policiesInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicy],
|
||||
bindingsInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicyBinding],
|
||||
authz authorizer.Authorizer,
|
||||
policiesInformer generic.Informer[*v1beta1.ValidatingAdmissionPolicy],
|
||||
bindingsInformer generic.Informer[*v1beta1.ValidatingAdmissionPolicyBinding],
|
||||
) *policyController {
|
||||
res := &policyController{}
|
||||
*res = policyController{
|
||||
filterCompiler: filterCompiler,
|
||||
typeChecker: typeChecker,
|
||||
definitionInfo: make(map[namespacedName]*definitionInfo),
|
||||
bindingInfos: make(map[namespacedName]*bindingInfo),
|
||||
paramsCRDControllers: make(map[v1alpha1.ParamKind]*paramInfo),
|
||||
paramsCRDControllers: make(map[v1beta1.ParamKind]*paramInfo),
|
||||
definitionsToBindings: make(map[namespacedName]sets.Set[namespacedName]),
|
||||
matcher: matcher,
|
||||
newValidator: NewValidator,
|
||||
@ -139,10 +124,10 @@ func newPolicyController(
|
||||
Name: "cel-policy-bindings",
|
||||
},
|
||||
),
|
||||
restMapper: restMapper,
|
||||
dynamicClient: dynamicClient,
|
||||
client: client,
|
||||
authz: authz,
|
||||
restMapper: restMapper,
|
||||
dynamicClient: dynamicClient,
|
||||
informerFactory: informerFactory,
|
||||
client: client,
|
||||
}
|
||||
return res
|
||||
}
|
||||
@ -175,20 +160,14 @@ func (c *policyController) HasSynced() bool {
|
||||
return c.policyDefinitionsController.HasSynced() && c.policyBindingController.HasSynced()
|
||||
}
|
||||
|
||||
func (c *policyController) reconcilePolicyDefinition(namespace, name string, definition *v1alpha1.ValidatingAdmissionPolicy) error {
|
||||
func (c *policyController) reconcilePolicyDefinition(namespace, name string, definition *v1beta1.ValidatingAdmissionPolicy) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
err := c.reconcilePolicyDefinitionSpec(namespace, name, definition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.typeChecker != nil {
|
||||
err = c.reconcilePolicyStatus(namespace, name, definition)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *policyController) reconcilePolicyDefinitionSpec(namespace, name string, definition *v1alpha1.ValidatingAdmissionPolicy) error {
|
||||
func (c *policyController) reconcilePolicyDefinitionSpec(namespace, name string, definition *v1beta1.ValidatingAdmissionPolicy) error {
|
||||
c.cachedPolicies = nil // invalidate cachedPolicies
|
||||
|
||||
// Namespace for policydefinition is empty.
|
||||
@ -207,7 +186,7 @@ func (c *policyController) reconcilePolicyDefinitionSpec(namespace, name string,
|
||||
return nil
|
||||
}
|
||||
|
||||
var paramSource *v1alpha1.ParamKind
|
||||
var paramSource *v1beta1.ParamKind
|
||||
if definition != nil {
|
||||
paramSource = definition.Spec.ParamKind
|
||||
}
|
||||
@ -253,7 +232,6 @@ func (c *policyController) reconcilePolicyDefinitionSpec(namespace, name string,
|
||||
// Skip setting up controller for empty param type
|
||||
return nil
|
||||
}
|
||||
|
||||
// find GVR for params
|
||||
// Parse param source into a GVK
|
||||
|
||||
@ -280,104 +258,78 @@ func (c *policyController) reconcilePolicyDefinitionSpec(namespace, name string,
|
||||
return info.configurationError
|
||||
}
|
||||
|
||||
if info, ok := c.paramsCRDControllers[*paramSource]; ok {
|
||||
// If a param controller is already active for this paramsource, make
|
||||
// sure it is tracking this policy's dependency upon it
|
||||
info.dependentDefinitions.Insert(nn)
|
||||
|
||||
} else {
|
||||
instanceContext, instanceCancel := context.WithCancel(c.context)
|
||||
|
||||
var informer cache.SharedIndexInformer
|
||||
|
||||
// Informer Factory is optional
|
||||
if c.client != nil {
|
||||
// Create temporary informer factory
|
||||
// Cannot use the k8s shared informer factory for dynamic params informer.
|
||||
// Would leak unnecessary informers when we are done since we would have to
|
||||
// call informerFactory.Start() with a longer-lived stopCh than necessary.
|
||||
// SharedInformerFactory does not support temporary usage.
|
||||
dynamicFactory := informers.NewSharedInformerFactory(c.client, 10*time.Minute)
|
||||
|
||||
// Look for a typed informer. If it does not exist
|
||||
genericInformer, err := dynamicFactory.ForResource(paramsGVR.Resource)
|
||||
|
||||
// Ignore error. We fallback to dynamic informer if there is no
|
||||
// typed informer
|
||||
if err != nil {
|
||||
informer = nil
|
||||
} else {
|
||||
informer = genericInformer.Informer()
|
||||
|
||||
// Set transformer on the informer to workaround inconsistency
|
||||
// where typed objects have TypeMeta wiped out but dynamic
|
||||
// objects keep kind/apiVersion fields
|
||||
informer.SetTransform(func(i interface{}) (interface{}, error) {
|
||||
// Ensure param is populated with its GVK for consistency
|
||||
// (CRD dynamic informer always returns objects with kind/apiversion,
|
||||
// but native types do not include populated TypeMeta.
|
||||
if param := i.(runtime.Object); param != nil {
|
||||
if param.GetObjectKind().GroupVersionKind().Empty() {
|
||||
// https://github.com/kubernetes/client-go/issues/413#issue-324586398
|
||||
gvks, _, _ := k8sscheme.Scheme.ObjectKinds(param)
|
||||
for _, gvk := range gvks {
|
||||
if len(gvk.Kind) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
|
||||
continue
|
||||
}
|
||||
param.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if informer == nil {
|
||||
// Dynamic JSON informer fallback.
|
||||
// Cannot use shared dynamic informer since it would be impossible
|
||||
// to clean CRD informers properly with multiple dependents
|
||||
// (cannot start ahead of time, and cannot track dependencies via stopCh)
|
||||
informer = dynamicinformer.NewFilteredDynamicInformer(
|
||||
c.dynamicClient,
|
||||
paramsGVR.Resource,
|
||||
corev1.NamespaceAll,
|
||||
// Use same interval as is used for k8s typed sharedInformerFactory
|
||||
// https://github.com/kubernetes/kubernetes/blob/7e0923899fed622efbc8679cca6b000d43633e38/cmd/kube-apiserver/app/server.go#L430
|
||||
10*time.Minute,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
nil,
|
||||
).Informer()
|
||||
}
|
||||
|
||||
controller := generic.NewController(
|
||||
generic.NewInformer[runtime.Object](informer),
|
||||
c.reconcileParams,
|
||||
generic.ControllerOptions{
|
||||
Workers: 1,
|
||||
Name: paramSource.String() + "-controller",
|
||||
},
|
||||
)
|
||||
|
||||
c.paramsCRDControllers[*paramSource] = ¶mInfo{
|
||||
controller: controller,
|
||||
stop: instanceCancel,
|
||||
dependentDefinitions: sets.New(nn),
|
||||
}
|
||||
|
||||
go controller.Run(instanceContext)
|
||||
go informer.Run(instanceContext.Done())
|
||||
}
|
||||
paramInfo := c.ensureParamInfo(paramSource, paramsGVR)
|
||||
paramInfo.dependentDefinitions.Insert(nn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *policyController) reconcilePolicyBinding(namespace, name string, binding *v1alpha1.ValidatingAdmissionPolicyBinding) error {
|
||||
// Ensures that there is an informer started for the given GVK to be used as a
|
||||
// param
|
||||
func (c *policyController) ensureParamInfo(paramSource *v1beta1.ParamKind, mapping *meta.RESTMapping) *paramInfo {
|
||||
if info, ok := c.paramsCRDControllers[*paramSource]; ok {
|
||||
return info
|
||||
}
|
||||
|
||||
// We are not watching this param. Start an informer for it.
|
||||
instanceContext, instanceCancel := context.WithCancel(c.context)
|
||||
|
||||
var informer cache.SharedIndexInformer
|
||||
|
||||
// Try to see if our provided informer factory has an informer for this type.
|
||||
// We assume the informer is already started, and starts all types associated
|
||||
// with it.
|
||||
if genericInformer, err := c.informerFactory.ForResource(mapping.Resource); err == nil {
|
||||
informer = genericInformer.Informer()
|
||||
|
||||
// Ensure the informer is started
|
||||
// Use policyController's context rather than the instance context.
|
||||
// PolicyController context is expected to last until app shutdown
|
||||
// This is due to behavior of informerFactory which would cause the
|
||||
// informer to stop running once the context is cancelled, and
|
||||
// never started again.
|
||||
c.informerFactory.Start(c.context.Done())
|
||||
} else {
|
||||
// Dynamic JSON informer fallback.
|
||||
// Cannot use shared dynamic informer since it would be impossible
|
||||
// to clean CRD informers properly with multiple dependents
|
||||
// (cannot start ahead of time, and cannot track dependencies via stopCh)
|
||||
informer = dynamicinformer.NewFilteredDynamicInformer(
|
||||
c.dynamicClient,
|
||||
mapping.Resource,
|
||||
corev1.NamespaceAll,
|
||||
// Use same interval as is used for k8s typed sharedInformerFactory
|
||||
// https://github.com/kubernetes/kubernetes/blob/7e0923899fed622efbc8679cca6b000d43633e38/cmd/kube-apiserver/app/server.go#L430
|
||||
10*time.Minute,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
nil,
|
||||
).Informer()
|
||||
go informer.Run(instanceContext.Done())
|
||||
}
|
||||
|
||||
controller := generic.NewController(
|
||||
generic.NewInformer[runtime.Object](informer),
|
||||
c.reconcileParams,
|
||||
generic.ControllerOptions{
|
||||
Workers: 1,
|
||||
Name: paramSource.String() + "-controller",
|
||||
},
|
||||
)
|
||||
|
||||
ret := ¶mInfo{
|
||||
controller: controller,
|
||||
stop: instanceCancel,
|
||||
scope: mapping.Scope,
|
||||
dependentDefinitions: sets.New[namespacedName](),
|
||||
}
|
||||
c.paramsCRDControllers[*paramSource] = ret
|
||||
|
||||
go controller.Run(instanceContext)
|
||||
return ret
|
||||
|
||||
}
|
||||
|
||||
func (c *policyController) reconcilePolicyBinding(namespace, name string, binding *v1beta1.ValidatingAdmissionPolicyBinding) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
@ -443,30 +395,6 @@ func (c *policyController) reconcilePolicyBinding(namespace, name string, bindin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *policyController) reconcilePolicyStatus(namespace, name string, definition *v1alpha1.ValidatingAdmissionPolicy) error {
|
||||
if definition != nil && definition.Status.ObservedGeneration < definition.Generation {
|
||||
st := c.calculatePolicyStatus(definition)
|
||||
newDefinition := definition.DeepCopy()
|
||||
newDefinition.Status = *st
|
||||
_, err := c.client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().UpdateStatus(c.context, newDefinition, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
// ignore error when the controller is not able to
|
||||
// mutate the definition, and to avoid infinite requeue.
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *policyController) calculatePolicyStatus(definition *v1alpha1.ValidatingAdmissionPolicy) *v1alpha1.ValidatingAdmissionPolicyStatus {
|
||||
expressionWarnings := c.typeChecker.Check(definition)
|
||||
// modifying a deepcopy of the original status, preserving unrelated existing data
|
||||
status := definition.Status.DeepCopy()
|
||||
status.ObservedGeneration = definition.Generation
|
||||
status.TypeChecking = &v1alpha1.TypeChecking{ExpressionWarnings: expressionWarnings}
|
||||
return status
|
||||
}
|
||||
|
||||
func (c *policyController) reconcileParams(namespace, name string, params runtime.Object) error {
|
||||
// Do nothing.
|
||||
// When we add informational type checking we will need to compile in the
|
||||
@ -504,39 +432,49 @@ func (c *policyController) latestPolicyData() []policyData {
|
||||
}
|
||||
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true}
|
||||
expressionOptionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}
|
||||
failurePolicy := convertv1alpha1FailurePolicyTypeTov1FailurePolicyType(definitionInfo.lastReconciledValue.Spec.FailurePolicy)
|
||||
failurePolicy := convertv1beta1FailurePolicyTypeTov1FailurePolicyType(definitionInfo.lastReconciledValue.Spec.FailurePolicy)
|
||||
var matcher matchconditions.Matcher = nil
|
||||
matchConditions := definitionInfo.lastReconciledValue.Spec.MatchConditions
|
||||
|
||||
filterCompiler := c.filterCompiler
|
||||
if filterCompiler == nil {
|
||||
compositedCompiler, err := cel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))
|
||||
if err == nil {
|
||||
filterCompiler = compositedCompiler
|
||||
compositedCompiler.CompileAndStoreVariables(convertv1beta1Variables(definitionInfo.lastReconciledValue.Spec.Variables), optionalVars, environment.StoredExpressions)
|
||||
} else {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}
|
||||
if len(matchConditions) > 0 {
|
||||
matchExpressionAccessors := make([]cel.ExpressionAccessor, len(matchConditions))
|
||||
for i := range matchConditions {
|
||||
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
|
||||
}
|
||||
matcher = matchconditions.NewMatcher(c.filterCompiler.Compile(matchExpressionAccessors, optionalVars, celconfig.PerCallLimit), c.authz, failurePolicy, "validatingadmissionpolicy", definitionInfo.lastReconciledValue.Name)
|
||||
matcher = matchconditions.NewMatcher(filterCompiler.Compile(matchExpressionAccessors, optionalVars, environment.StoredExpressions), failurePolicy, "policy", "validate", definitionInfo.lastReconciledValue.Name)
|
||||
}
|
||||
bindingInfo.validator = c.newValidator(
|
||||
c.filterCompiler.Compile(convertv1alpha1Validations(definitionInfo.lastReconciledValue.Spec.Validations), optionalVars, celconfig.PerCallLimit),
|
||||
filterCompiler.Compile(convertv1beta1Validations(definitionInfo.lastReconciledValue.Spec.Validations), optionalVars, environment.StoredExpressions),
|
||||
matcher,
|
||||
c.filterCompiler.Compile(convertv1alpha1AuditAnnotations(definitionInfo.lastReconciledValue.Spec.AuditAnnotations), optionalVars, celconfig.PerCallLimit),
|
||||
c.filterCompiler.Compile(convertV1Alpha1MessageExpressions(definitionInfo.lastReconciledValue.Spec.Validations), expressionOptionalVars, celconfig.PerCallLimit),
|
||||
filterCompiler.Compile(convertv1beta1AuditAnnotations(definitionInfo.lastReconciledValue.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions),
|
||||
filterCompiler.Compile(convertv1beta1MessageExpressions(definitionInfo.lastReconciledValue.Spec.Validations), expressionOptionalVars, environment.StoredExpressions),
|
||||
failurePolicy,
|
||||
c.authz,
|
||||
)
|
||||
}
|
||||
bindingInfos = append(bindingInfos, *bindingInfo)
|
||||
}
|
||||
|
||||
var paramController generic.Controller[runtime.Object]
|
||||
var pInfo paramInfo
|
||||
if paramKind := definitionInfo.lastReconciledValue.Spec.ParamKind; paramKind != nil {
|
||||
if info, ok := c.paramsCRDControllers[*paramKind]; ok {
|
||||
paramController = info.controller
|
||||
pInfo = *info
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, policyData{
|
||||
definitionInfo: *definitionInfo,
|
||||
paramController: paramController,
|
||||
bindings: bindingInfos,
|
||||
definitionInfo: *definitionInfo,
|
||||
paramInfo: pInfo,
|
||||
bindings: bindingInfos,
|
||||
})
|
||||
}
|
||||
|
||||
@ -544,21 +482,21 @@ func (c *policyController) latestPolicyData() []policyData {
|
||||
return res
|
||||
}
|
||||
|
||||
func convertv1alpha1FailurePolicyTypeTov1FailurePolicyType(policyType *v1alpha1.FailurePolicyType) *v1.FailurePolicyType {
|
||||
func convertv1beta1FailurePolicyTypeTov1FailurePolicyType(policyType *v1beta1.FailurePolicyType) *v1.FailurePolicyType {
|
||||
if policyType == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var v1FailPolicy v1.FailurePolicyType
|
||||
if *policyType == v1alpha1.Fail {
|
||||
if *policyType == v1beta1.Fail {
|
||||
v1FailPolicy = v1.Fail
|
||||
} else if *policyType == v1alpha1.Ignore {
|
||||
} else if *policyType == v1beta1.Ignore {
|
||||
v1FailPolicy = v1.Ignore
|
||||
}
|
||||
return &v1FailPolicy
|
||||
}
|
||||
|
||||
func convertv1alpha1Validations(inputValidations []v1alpha1.Validation) []cel.ExpressionAccessor {
|
||||
func convertv1beta1Validations(inputValidations []v1beta1.Validation) []cel.ExpressionAccessor {
|
||||
celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations))
|
||||
for i, validation := range inputValidations {
|
||||
validation := ValidationCondition{
|
||||
@ -571,7 +509,7 @@ func convertv1alpha1Validations(inputValidations []v1alpha1.Validation) []cel.Ex
|
||||
return celExpressionAccessor
|
||||
}
|
||||
|
||||
func convertV1Alpha1MessageExpressions(inputValidations []v1alpha1.Validation) []cel.ExpressionAccessor {
|
||||
func convertv1beta1MessageExpressions(inputValidations []v1beta1.Validation) []cel.ExpressionAccessor {
|
||||
celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations))
|
||||
for i, validation := range inputValidations {
|
||||
if validation.MessageExpression != "" {
|
||||
@ -584,7 +522,7 @@ func convertV1Alpha1MessageExpressions(inputValidations []v1alpha1.Validation) [
|
||||
return celExpressionAccessor
|
||||
}
|
||||
|
||||
func convertv1alpha1AuditAnnotations(inputValidations []v1alpha1.AuditAnnotation) []cel.ExpressionAccessor {
|
||||
func convertv1beta1AuditAnnotations(inputValidations []v1beta1.AuditAnnotation) []cel.ExpressionAccessor {
|
||||
celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations))
|
||||
for i, validation := range inputValidations {
|
||||
validation := AuditAnnotationCondition{
|
||||
@ -596,6 +534,14 @@ func convertv1alpha1AuditAnnotations(inputValidations []v1alpha1.AuditAnnotation
|
||||
return celExpressionAccessor
|
||||
}
|
||||
|
||||
func convertv1beta1Variables(variables []v1beta1.Variable) []cel.NamedExpressionAccessor {
|
||||
namedExpressions := make([]cel.NamedExpressionAccessor, len(variables))
|
||||
for i, variable := range variables {
|
||||
namedExpressions[i] = &Variable{Name: variable.Name, Expression: variable.Expression}
|
||||
}
|
||||
return namedExpressions
|
||||
}
|
||||
|
||||
func getNamespaceName(namespace, name string) namespacedName {
|
||||
return namespacedName{
|
||||
namespace: namespace,
|
||||
|
32
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go
generated
vendored
32
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/interface.go
generated
vendored
@ -21,12 +21,14 @@ import (
|
||||
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
var _ cel.ExpressionAccessor = &ValidationCondition{}
|
||||
@ -60,17 +62,39 @@ func (v *AuditAnnotationCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.StringType, celgo.NullType}
|
||||
}
|
||||
|
||||
// Variable is a named expression for composition.
|
||||
type Variable struct {
|
||||
Name string
|
||||
Expression string
|
||||
}
|
||||
|
||||
func (v *Variable) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *Variable) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.AnyType, celgo.DynType}
|
||||
}
|
||||
|
||||
func (v *Variable) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
// Matcher is used for matching ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding to attributes
|
||||
type Matcher interface {
|
||||
admission.InitializationValidator
|
||||
|
||||
// DefinitionMatches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionKind, error)
|
||||
DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error)
|
||||
|
||||
// BindingMatches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicyBinding) (bool, error)
|
||||
BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicyBinding) (bool, error)
|
||||
|
||||
// GetNamespace retrieves the Namespace resource by the given name. The name may be empty, in which case
|
||||
// GetNamespace must return nil, nil
|
||||
GetNamespace(name string) (*corev1.Namespace, error)
|
||||
}
|
||||
|
||||
// ValidateResult defines the result of a Validator.Validate operation.
|
||||
@ -85,5 +109,5 @@ type ValidateResult struct {
|
||||
type Validator interface {
|
||||
// Validate is used to take cel evaluations and convert into decisions
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult
|
||||
Validate(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||
}
|
||||
|
17
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matcher.go
generated
vendored
17
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matcher.go
generated
vendored
@ -17,7 +17,8 @@ limitations under the License.
|
||||
package validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@ -28,7 +29,7 @@ import (
|
||||
var _ matching.MatchCriteria = &matchCriteria{}
|
||||
|
||||
type matchCriteria struct {
|
||||
constraints *v1alpha1.MatchResources
|
||||
constraints *v1beta1.MatchResources
|
||||
}
|
||||
|
||||
// GetParsedNamespaceSelector returns the converted LabelSelector which implements labels.Selector
|
||||
@ -42,7 +43,7 @@ func (m *matchCriteria) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
}
|
||||
|
||||
// GetMatchResources returns the matchConstraints
|
||||
func (m *matchCriteria) GetMatchResources() v1alpha1.MatchResources {
|
||||
func (m *matchCriteria) GetMatchResources() v1beta1.MatchResources {
|
||||
return *m.constraints
|
||||
}
|
||||
|
||||
@ -62,17 +63,21 @@ func (c *matcher) ValidateInitialization() error {
|
||||
}
|
||||
|
||||
// DefinitionMatches returns whether this ValidatingAdmissionPolicy matches the provided admission resource request
|
||||
func (c *matcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionKind, error) {
|
||||
func (c *matcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
criteria := matchCriteria{constraints: definition.Spec.MatchConstraints}
|
||||
return c.Matcher.Matches(a, o, &criteria)
|
||||
}
|
||||
|
||||
// BindingMatches returns whether this ValidatingAdmissionPolicyBinding matches the provided admission resource request
|
||||
func (c *matcher) BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding *v1alpha1.ValidatingAdmissionPolicyBinding) (bool, error) {
|
||||
func (c *matcher) BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding *v1beta1.ValidatingAdmissionPolicyBinding) (bool, error) {
|
||||
if binding.Spec.MatchResources == nil {
|
||||
return true, nil
|
||||
}
|
||||
criteria := matchCriteria{constraints: binding.Spec.MatchResources}
|
||||
isMatch, _, err := c.Matcher.Matches(a, o, &criteria)
|
||||
isMatch, _, _, err := c.Matcher.Matches(a, o, &criteria)
|
||||
return isMatch, err
|
||||
}
|
||||
|
||||
func (c *matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return c.Matcher.GetNamespace(name)
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -35,7 +36,7 @@ type MatchCriteria interface {
|
||||
namespace.NamespaceSelectorProvider
|
||||
object.ObjectSelectorProvider
|
||||
|
||||
GetMatchResources() v1alpha1.MatchResources
|
||||
GetMatchResources() v1beta1.MatchResources
|
||||
}
|
||||
|
||||
// Matcher decides if a request matches against matchCriteria
|
||||
@ -44,6 +45,10 @@ type Matcher struct {
|
||||
objectMatcher *object.Matcher
|
||||
}
|
||||
|
||||
func (m *Matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return m.namespaceMatcher.GetNamespace(name)
|
||||
}
|
||||
|
||||
// NewMatcher initialize the matcher with dependencies requires
|
||||
func NewMatcher(
|
||||
namespaceLister listersv1.NamespaceLister,
|
||||
@ -66,56 +71,60 @@ func (m *Matcher) ValidateInitialization() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionKind, error) {
|
||||
func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
matches, matchNsErr := m.namespaceMatcher.MatchNamespaceSelector(criteria, attr)
|
||||
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchNsErr == nil {
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
matches, matchObjErr := m.objectMatcher.MatchObjectSelector(criteria, attr)
|
||||
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchObjErr == nil {
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
matchResources := criteria.GetMatchResources()
|
||||
matchPolicy := matchResources.MatchPolicy
|
||||
if isExcluded, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil {
|
||||
return false, schema.GroupVersionKind{}, err
|
||||
if isExcluded, _, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, err
|
||||
}
|
||||
|
||||
var (
|
||||
isMatch bool
|
||||
matchKind schema.GroupVersionKind
|
||||
matchErr error
|
||||
isMatch bool
|
||||
matchResource schema.GroupVersionResource
|
||||
matchKind schema.GroupVersionKind
|
||||
matchErr error
|
||||
)
|
||||
if len(matchResources.ResourceRules) == 0 {
|
||||
isMatch = true
|
||||
matchKind = attr.GetKind()
|
||||
matchResource = attr.GetResource()
|
||||
} else {
|
||||
isMatch, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o)
|
||||
isMatch, matchResource, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o)
|
||||
}
|
||||
if matchErr != nil {
|
||||
return false, schema.GroupVersionKind{}, matchErr
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchErr
|
||||
}
|
||||
if !isMatch {
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
// now that we know this applies to this request otherwise, if there were selector errors, return them
|
||||
if matchNsErr != nil {
|
||||
return false, schema.GroupVersionKind{}, matchNsErr
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchNsErr
|
||||
}
|
||||
if matchObjErr != nil {
|
||||
return false, schema.GroupVersionKind{}, matchObjErr
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchObjErr
|
||||
}
|
||||
|
||||
return true, matchKind, nil
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
|
||||
func matchesResourceRules(namedRules []v1alpha1.NamedRuleWithOperations, matchPolicy *v1alpha1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionKind, error) {
|
||||
func matchesResourceRules(namedRules []v1beta1.NamedRuleWithOperations, matchPolicy *v1beta1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
matchKind := attr.GetKind()
|
||||
matchResource := attr.GetResource()
|
||||
|
||||
for _, namedRule := range namedRules {
|
||||
rule := v1.RuleWithOperations(namedRule.RuleWithOperations)
|
||||
ruleMatcher := rules.Matcher{
|
||||
@ -127,22 +136,22 @@ func matchesResourceRules(namedRules []v1alpha1.NamedRuleWithOperations, matchPo
|
||||
}
|
||||
// an empty name list always matches
|
||||
if len(namedRule.ResourceNames) == 0 {
|
||||
return true, matchKind, nil
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
// TODO: GetName() can return an empty string if the user is relying on
|
||||
// the API server to generate the name... figure out what to do for this edge case
|
||||
name := attr.GetName()
|
||||
for _, matchedName := range namedRule.ResourceNames {
|
||||
if name == matchedName {
|
||||
return true, matchKind, nil
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if match policy is undefined or exact, don't perform fuzzy matching
|
||||
// note that defaulting to fuzzy matching is set by the API
|
||||
if matchPolicy == nil || *matchPolicy == v1alpha1.Exact {
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
if matchPolicy == nil || *matchPolicy == v1beta1.Exact {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
attrWithOverride := &attrWithResourceOverride{Attributes: attr}
|
||||
@ -164,11 +173,11 @@ func matchesResourceRules(namedRules []v1alpha1.NamedRuleWithOperations, matchPo
|
||||
}
|
||||
matchKind = o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource())
|
||||
if matchKind.Empty() {
|
||||
return false, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent)
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent)
|
||||
}
|
||||
// an empty name list always matches
|
||||
if len(namedRule.ResourceNames) == 0 {
|
||||
return true, matchKind, nil
|
||||
return true, equivalent, matchKind, nil
|
||||
}
|
||||
|
||||
// TODO: GetName() can return an empty string if the user is relying on
|
||||
@ -176,12 +185,12 @@ func matchesResourceRules(namedRules []v1alpha1.NamedRuleWithOperations, matchPo
|
||||
name := attr.GetName()
|
||||
for _, matchedName := range namedRule.ResourceNames {
|
||||
if name == matchedName {
|
||||
return true, matchKind, nil
|
||||
return true, equivalent, matchKind, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, schema.GroupVersionKind{}, nil
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
type attrWithResourceOverride struct {
|
||||
|
334
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/typechecking.go
generated
vendored
334
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/typechecking.go
generated
vendored
@ -21,19 +21,20 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
"k8s.io/apiserver/pkg/cel/openapi"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
@ -43,8 +44,17 @@ import (
|
||||
const maxTypesToCheck = 10
|
||||
|
||||
type TypeChecker struct {
|
||||
schemaResolver resolver.SchemaResolver
|
||||
restMapper meta.RESTMapper
|
||||
SchemaResolver resolver.SchemaResolver
|
||||
RestMapper meta.RESTMapper
|
||||
}
|
||||
|
||||
// TypeCheckingContext holds information about the policy being type-checked.
|
||||
// The struct is opaque to the caller.
|
||||
type TypeCheckingContext struct {
|
||||
gvks []schema.GroupVersionKind
|
||||
declTypes []*apiservercel.DeclType
|
||||
paramGVK schema.GroupVersionKind
|
||||
paramDeclType *apiservercel.DeclType
|
||||
}
|
||||
|
||||
type typeOverwrite struct {
|
||||
@ -52,127 +62,148 @@ type typeOverwrite struct {
|
||||
params *apiservercel.DeclType
|
||||
}
|
||||
|
||||
// typeCheckingResult holds the issues found during type checking, any returned
|
||||
// TypeCheckingResult holds the issues found during type checking, any returned
|
||||
// error, and the gvk that the type checking is performed against.
|
||||
type typeCheckingResult struct {
|
||||
gvk schema.GroupVersionKind
|
||||
type TypeCheckingResult struct {
|
||||
// GVK is the associated GVK
|
||||
GVK schema.GroupVersionKind
|
||||
// Issues contain machine-readable information about the typechecking result.
|
||||
Issues *cel.Issues
|
||||
// Err is the possible error that was encounter during type checking.
|
||||
Err error
|
||||
}
|
||||
|
||||
issues *cel.Issues
|
||||
err error
|
||||
// TypeCheckingResults is a collection of TypeCheckingResult
|
||||
type TypeCheckingResults []*TypeCheckingResult
|
||||
|
||||
func (rs TypeCheckingResults) String() string {
|
||||
var messages []string
|
||||
for _, r := range rs {
|
||||
message := r.String()
|
||||
if message != "" {
|
||||
messages = append(messages, message)
|
||||
}
|
||||
}
|
||||
return strings.Join(messages, "\n")
|
||||
}
|
||||
|
||||
// String converts the result to human-readable form as a string.
|
||||
func (r *TypeCheckingResult) String() string {
|
||||
if r.Issues == nil && r.Err == nil {
|
||||
return ""
|
||||
}
|
||||
if r.Err != nil {
|
||||
return fmt.Sprintf("%v: type checking error: %v\n", r.GVK, r.Err)
|
||||
}
|
||||
return fmt.Sprintf("%v: %s\n", r.GVK, r.Issues)
|
||||
}
|
||||
|
||||
// Check preforms the type check against the given policy, and format the result
|
||||
// as []ExpressionWarning that is ready to be set in policy.Status
|
||||
// The result is nil if type checking returns no warning.
|
||||
// The policy object is NOT mutated. The caller should update Status accordingly
|
||||
func (c *TypeChecker) Check(policy *v1alpha1.ValidatingAdmissionPolicy) []v1alpha1.ExpressionWarning {
|
||||
exps := make([]string, 0, len(policy.Spec.Validations))
|
||||
// check main validation expressions, located in spec.validations[*]
|
||||
func (c *TypeChecker) Check(policy *v1beta1.ValidatingAdmissionPolicy) []v1beta1.ExpressionWarning {
|
||||
ctx := c.CreateContext(policy)
|
||||
|
||||
// warnings to return, note that the capacity is optimistically set to zero
|
||||
var warnings []v1beta1.ExpressionWarning // intentionally not setting capacity
|
||||
|
||||
// check main validation expressions and their message expressions, located in spec.validations[*]
|
||||
fieldRef := field.NewPath("spec", "validations")
|
||||
for _, v := range policy.Spec.Validations {
|
||||
exps = append(exps, v.Expression)
|
||||
}
|
||||
msgs := c.CheckExpressions(exps, policy.Spec.ParamKind != nil, policy)
|
||||
var results []v1alpha1.ExpressionWarning // intentionally not setting capacity
|
||||
for i, msg := range msgs {
|
||||
if msg != "" {
|
||||
results = append(results, v1alpha1.ExpressionWarning{
|
||||
for i, v := range policy.Spec.Validations {
|
||||
results := c.CheckExpression(ctx, v.Expression)
|
||||
if len(results) != 0 {
|
||||
warnings = append(warnings, v1beta1.ExpressionWarning{
|
||||
FieldRef: fieldRef.Index(i).Child("expression").String(),
|
||||
Warning: msg,
|
||||
Warning: results.String(),
|
||||
})
|
||||
}
|
||||
// Note that MessageExpression is optional
|
||||
if v.MessageExpression == "" {
|
||||
continue
|
||||
}
|
||||
results = c.CheckExpression(ctx, v.MessageExpression)
|
||||
if len(results) != 0 {
|
||||
warnings = append(warnings, v1beta1.ExpressionWarning{
|
||||
FieldRef: fieldRef.Index(i).Child("messageExpression").String(),
|
||||
Warning: results.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
// CheckExpressions checks a set of compiled CEL programs against the GVKs defined in
|
||||
// policy.Spec.MatchConstraints
|
||||
// The result is a human-readable form that describe which expressions
|
||||
// violate what types at what place. The indexes of the return []string
|
||||
// matches these of the input expressions.
|
||||
// TODO: It is much more useful to have machine-readable output and let the
|
||||
// client format it. That requires an update to the KEP, probably in coming
|
||||
// releases.
|
||||
func (c *TypeChecker) CheckExpressions(expressions []string, hasParams bool, policy *v1alpha1.ValidatingAdmissionPolicy) []string {
|
||||
var allWarnings []string
|
||||
// CreateContext resolves all types and their schemas from a policy definition and creates the context.
|
||||
func (c *TypeChecker) CreateContext(policy *v1beta1.ValidatingAdmissionPolicy) *TypeCheckingContext {
|
||||
ctx := new(TypeCheckingContext)
|
||||
allGvks := c.typesToCheck(policy)
|
||||
gvks := make([]schema.GroupVersionKind, 0, len(allGvks))
|
||||
schemas := make([]common.Schema, 0, len(allGvks))
|
||||
declTypes := make([]*apiservercel.DeclType, 0, len(allGvks))
|
||||
for _, gvk := range allGvks {
|
||||
s, err := c.schemaResolver.ResolveSchema(gvk)
|
||||
declType, err := c.declType(gvk)
|
||||
if err != nil {
|
||||
// type checking errors MUST NOT alter the behavior of the policy
|
||||
// even if an error occurs.
|
||||
if !errors.Is(err, resolver.ErrSchemaNotFound) {
|
||||
// Anything except ErrSchemaNotFound is an internal error
|
||||
klog.ErrorS(err, "internal error: schema resolution failure", "gvk", gvk)
|
||||
klog.V(2).ErrorS(err, "internal error: schema resolution failure", "gvk", gvk)
|
||||
}
|
||||
// skip if an unrecoverable error occurs.
|
||||
// skip for not found or internal error
|
||||
continue
|
||||
}
|
||||
gvks = append(gvks, gvk)
|
||||
schemas = append(schemas, &openapi.Schema{Schema: s})
|
||||
declTypes = append(declTypes, declType)
|
||||
}
|
||||
ctx.gvks = gvks
|
||||
ctx.declTypes = declTypes
|
||||
|
||||
paramsType := c.paramsType(policy)
|
||||
paramsDeclType, err := c.declType(paramsType)
|
||||
paramsGVK := c.paramsGVK(policy) // maybe empty, correctly handled
|
||||
paramsDeclType, err := c.declType(paramsGVK)
|
||||
if err != nil {
|
||||
if !errors.Is(err, resolver.ErrSchemaNotFound) {
|
||||
klog.V(2).ErrorS(err, "cannot resolve schema for params", "gvk", paramsType)
|
||||
klog.V(2).ErrorS(err, "internal error: cannot resolve schema for params", "gvk", paramsGVK)
|
||||
}
|
||||
paramsDeclType = nil
|
||||
}
|
||||
|
||||
for _, exp := range expressions {
|
||||
var results []typeCheckingResult
|
||||
for i, gvk := range gvks {
|
||||
s := schemas[i]
|
||||
issues, err := c.checkExpression(exp, hasParams, typeOverwrite{
|
||||
object: common.SchemaDeclType(s, true),
|
||||
params: paramsDeclType,
|
||||
})
|
||||
// save even if no issues are found, for the sake of formatting.
|
||||
results = append(results, typeCheckingResult{
|
||||
gvk: gvk,
|
||||
issues: issues,
|
||||
err: err,
|
||||
})
|
||||
}
|
||||
allWarnings = append(allWarnings, c.formatWarning(results))
|
||||
}
|
||||
|
||||
return allWarnings
|
||||
ctx.paramGVK = paramsGVK
|
||||
ctx.paramDeclType = paramsDeclType
|
||||
return ctx
|
||||
}
|
||||
|
||||
// formatWarning converts the resulting issues and possible error during
|
||||
// type checking into a human-readable string
|
||||
func (c *TypeChecker) formatWarning(results []typeCheckingResult) string {
|
||||
var sb strings.Builder
|
||||
for _, result := range results {
|
||||
if result.issues == nil && result.err == nil {
|
||||
continue
|
||||
}
|
||||
if result.err != nil {
|
||||
sb.WriteString(fmt.Sprintf("%v: type checking error: %v\n", result.gvk, result.err))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v: %s\n", result.gvk, result.issues))
|
||||
// CheckExpression type checks a single expression, given the context
|
||||
func (c *TypeChecker) CheckExpression(ctx *TypeCheckingContext, expression string) TypeCheckingResults {
|
||||
var results TypeCheckingResults
|
||||
for i, gvk := range ctx.gvks {
|
||||
declType := ctx.declTypes[i]
|
||||
// TODO(jiahuif) hasAuthorizer always true for now, will change after expending type checking to all fields.
|
||||
issues, err := c.checkExpression(expression, ctx.paramDeclType != nil, true, typeOverwrite{
|
||||
object: declType,
|
||||
params: ctx.paramDeclType,
|
||||
})
|
||||
if issues != nil || err != nil {
|
||||
results = append(results, &TypeCheckingResult{Issues: issues, Err: err, GVK: gvk})
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(sb.String(), "\n")
|
||||
return results
|
||||
}
|
||||
|
||||
func generateUniqueTypeName(kind string) string {
|
||||
return fmt.Sprintf("%s%d", kind, time.Now().Nanosecond())
|
||||
}
|
||||
|
||||
func (c *TypeChecker) declType(gvk schema.GroupVersionKind) (*apiservercel.DeclType, error) {
|
||||
if gvk.Empty() {
|
||||
return nil, nil
|
||||
}
|
||||
s, err := c.schemaResolver.ResolveSchema(gvk)
|
||||
s, err := c.SchemaResolver.ResolveSchema(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return common.SchemaDeclType(&openapi.Schema{Schema: s}, true), nil
|
||||
return common.SchemaDeclType(&openapi.Schema{Schema: s}, true).MaybeAssignTypeName(generateUniqueTypeName(gvk.Kind)), nil
|
||||
}
|
||||
|
||||
func (c *TypeChecker) paramsType(policy *v1alpha1.ValidatingAdmissionPolicy) schema.GroupVersionKind {
|
||||
func (c *TypeChecker) paramsGVK(policy *v1beta1.ValidatingAdmissionPolicy) schema.GroupVersionKind {
|
||||
if policy.Spec.ParamKind == nil {
|
||||
return schema.GroupVersionKind{}
|
||||
}
|
||||
@ -183,8 +214,8 @@ func (c *TypeChecker) paramsType(policy *v1alpha1.ValidatingAdmissionPolicy) sch
|
||||
return gv.WithKind(policy.Spec.ParamKind.Kind)
|
||||
}
|
||||
|
||||
func (c *TypeChecker) checkExpression(expression string, hasParams bool, types typeOverwrite) (*cel.Issues, error) {
|
||||
env, err := buildEnv(hasParams, types)
|
||||
func (c *TypeChecker) checkExpression(expression string, hasParams, hasAuthorizer bool, types typeOverwrite) (*cel.Issues, error) {
|
||||
env, err := buildEnv(hasParams, hasAuthorizer, types)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -202,7 +233,7 @@ func (c *TypeChecker) checkExpression(expression string, hasParams bool, types t
|
||||
|
||||
// typesToCheck extracts a list of GVKs that needs type checking from the policy
|
||||
// the result is sorted in the order of Group, Version, and Kind
|
||||
func (c *TypeChecker) typesToCheck(p *v1alpha1.ValidatingAdmissionPolicy) []schema.GroupVersionKind {
|
||||
func (c *TypeChecker) typesToCheck(p *v1beta1.ValidatingAdmissionPolicy) []schema.GroupVersionKind {
|
||||
gvks := sets.New[schema.GroupVersionKind]()
|
||||
if p.Spec.MatchConstraints == nil || len(p.Spec.MatchConstraints.ResourceRules) == 0 {
|
||||
return nil
|
||||
@ -235,7 +266,7 @@ func (c *TypeChecker) typesToCheck(p *v1alpha1.ValidatingAdmissionPolicy) []sche
|
||||
Version: version,
|
||||
Resource: resource,
|
||||
}
|
||||
resolved, err := c.restMapper.KindsFor(gvr)
|
||||
resolved, err := c.RestMapper.KindsFor(gvr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -263,7 +294,7 @@ func (c *TypeChecker) typesToCheck(p *v1alpha1.ValidatingAdmissionPolicy) []sche
|
||||
return sortGVKList(gvks.UnsortedList())
|
||||
}
|
||||
|
||||
func extractGroups(rule *v1alpha1.Rule) []string {
|
||||
func extractGroups(rule *v1beta1.Rule) []string {
|
||||
groups := make([]string, 0, len(rule.APIGroups))
|
||||
for _, group := range rule.APIGroups {
|
||||
// give up if wildcard
|
||||
@ -275,7 +306,7 @@ func extractGroups(rule *v1alpha1.Rule) []string {
|
||||
return groups
|
||||
}
|
||||
|
||||
func extractVersions(rule *v1alpha1.Rule) []string {
|
||||
func extractVersions(rule *v1beta1.Rule) []string {
|
||||
versions := make([]string, 0, len(rule.APIVersions))
|
||||
for _, version := range rule.APIVersions {
|
||||
if strings.ContainsAny(version, "*") {
|
||||
@ -286,7 +317,7 @@ func extractVersions(rule *v1alpha1.Rule) []string {
|
||||
return versions
|
||||
}
|
||||
|
||||
func extractResources(rule *v1alpha1.Rule) []string {
|
||||
func extractResources(rule *v1beta1.Rule) []string {
|
||||
resources := make([]string, 0, len(rule.Resources))
|
||||
for _, resource := range rule.Resources {
|
||||
// skip wildcard and subresources
|
||||
@ -313,123 +344,64 @@ func sortGVKList(list []schema.GroupVersionKind) []schema.GroupVersionKind {
|
||||
return list
|
||||
}
|
||||
|
||||
func buildEnv(hasParams bool, types typeOverwrite) (*cel.Env, error) {
|
||||
baseEnv, err := getBaseEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reg := apiservercel.NewRegistry(baseEnv)
|
||||
func buildEnv(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*cel.Env, error) {
|
||||
baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
|
||||
requestType := plugincel.BuildRequestType()
|
||||
namespaceType := plugincel.BuildNamespaceType()
|
||||
|
||||
var varOpts []cel.EnvOption
|
||||
var rts []*apiservercel.RuleTypes
|
||||
var declTypes []*apiservercel.DeclType
|
||||
|
||||
// namespace, hand-crafted type
|
||||
declTypes = append(declTypes, namespaceType)
|
||||
varOpts = append(varOpts, createVariableOpts(namespaceType, plugincel.NamespaceVarName)...)
|
||||
|
||||
// request, hand-crafted type
|
||||
rt, opts, err := createRuleTypesAndOptions(reg, requestType, plugincel.RequestVarName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rts = append(rts, rt)
|
||||
varOpts = append(varOpts, opts...)
|
||||
declTypes = append(declTypes, requestType)
|
||||
varOpts = append(varOpts, createVariableOpts(requestType, plugincel.RequestVarName)...)
|
||||
|
||||
// object and oldObject, same type, type(s) resolved from constraints
|
||||
rt, opts, err = createRuleTypesAndOptions(reg, types.object, plugincel.ObjectVarName, plugincel.OldObjectVarName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rts = append(rts, rt)
|
||||
varOpts = append(varOpts, opts...)
|
||||
declTypes = append(declTypes, types.object)
|
||||
varOpts = append(varOpts, createVariableOpts(types.object, plugincel.ObjectVarName, plugincel.OldObjectVarName)...)
|
||||
|
||||
// params, defined by ParamKind
|
||||
if hasParams {
|
||||
rt, opts, err := createRuleTypesAndOptions(reg, types.params, plugincel.ParamsVarName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rts = append(rts, rt)
|
||||
varOpts = append(varOpts, opts...)
|
||||
if hasParams && types.params != nil {
|
||||
declTypes = append(declTypes, types.params)
|
||||
varOpts = append(varOpts, createVariableOpts(types.params, plugincel.ParamsVarName)...)
|
||||
}
|
||||
|
||||
opts, err = ruleTypesOpts(rts, baseEnv.TypeProvider())
|
||||
// authorizer, implicitly available to all expressions of a policy
|
||||
if hasAuthorizer {
|
||||
// we only need its structure but not the variable itself
|
||||
varOpts = append(varOpts, cel.Variable("authorizer", library.AuthorizerType))
|
||||
}
|
||||
|
||||
env, err := baseEnv.Extend(
|
||||
environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: varOpts,
|
||||
DeclTypes: declTypes,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, varOpts...) // add variables after ruleTypes.
|
||||
env, err := baseEnv.Extend(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return env, nil
|
||||
return env.Env(environment.StoredExpressions)
|
||||
}
|
||||
|
||||
// createRuleTypeAndOptions creates the cel RuleTypes and a slice of EnvOption
|
||||
// createVariableOpts creates a slice of EnvOption
|
||||
// that can be used for creating a CEL env containing variables of declType.
|
||||
// declType can be nil, in which case the variables will be of DynType.
|
||||
func createRuleTypesAndOptions(registry *apiservercel.Registry, declType *apiservercel.DeclType, variables ...string) (*apiservercel.RuleTypes, []cel.EnvOption, error) {
|
||||
func createVariableOpts(declType *apiservercel.DeclType, variables ...string) []cel.EnvOption {
|
||||
opts := make([]cel.EnvOption, 0, len(variables))
|
||||
// untyped, use DynType
|
||||
if declType == nil {
|
||||
for _, v := range variables {
|
||||
opts = append(opts, cel.Variable(v, cel.DynType))
|
||||
}
|
||||
return nil, opts, nil
|
||||
}
|
||||
// create a RuleType for the given type
|
||||
rt, err := apiservercel.NewRuleTypes(declType.TypeName(), declType, registry)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if rt == nil {
|
||||
return nil, nil, nil
|
||||
t := cel.DynType
|
||||
if declType != nil {
|
||||
t = declType.CelType()
|
||||
}
|
||||
for _, v := range variables {
|
||||
opts = append(opts, cel.Variable(v, declType.CelType()))
|
||||
opts = append(opts, cel.Variable(v, t))
|
||||
}
|
||||
return rt, opts, nil
|
||||
return opts
|
||||
}
|
||||
|
||||
func ruleTypesOpts(ruleTypes []*apiservercel.RuleTypes, underlyingTypeProvider ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
var providers []ref.TypeProvider // may be unused, too small to matter
|
||||
var adapters []ref.TypeAdapter
|
||||
for _, rt := range ruleTypes {
|
||||
if rt != nil {
|
||||
withTP, err := rt.WithTypeProvider(underlyingTypeProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers = append(providers, withTP)
|
||||
adapters = append(adapters, withTP)
|
||||
}
|
||||
}
|
||||
var tp ref.TypeProvider
|
||||
var ta ref.TypeAdapter
|
||||
switch len(providers) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
case 1:
|
||||
tp = providers[0]
|
||||
ta = adapters[0]
|
||||
default:
|
||||
tp = &apiservercel.CompositedTypeProvider{Providers: providers}
|
||||
ta = &apiservercel.CompositedTypeAdapter{Adapters: adapters}
|
||||
}
|
||||
return []cel.EnvOption{cel.CustomTypeProvider(tp), cel.CustomTypeAdapter(ta)}, nil
|
||||
}
|
||||
|
||||
func getBaseEnv() (*cel.Env, error) {
|
||||
typeCheckingBaseEnvInit.Do(func() {
|
||||
var opts []cel.EnvOption
|
||||
opts = append(opts, cel.HomogeneousAggregateLiterals())
|
||||
// Validate function declarations once during base env initialization,
|
||||
// so they don't need to be evaluated each time a CEL rule is compiled.
|
||||
// This is a relatively expensive operation.
|
||||
opts = append(opts, cel.EagerlyValidateDeclarations(true), cel.DefaultUTCTimeZone(true))
|
||||
opts = append(opts, library.ExtensionLibs...)
|
||||
typeCheckingBaseEnv, typeCheckingBaseEnvError = cel.NewEnv(opts...)
|
||||
})
|
||||
return typeCheckingBaseEnv, typeCheckingBaseEnvError
|
||||
}
|
||||
|
||||
var typeCheckingBaseEnv *cel.Env
|
||||
var typeCheckingBaseEnvError error
|
||||
var typeCheckingBaseEnvInit sync.Once
|
||||
|
23
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go
generated
vendored
23
vendor/k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/validator.go
generated
vendored
@ -24,8 +24,10 @@ import (
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
@ -42,17 +44,15 @@ type validator struct {
|
||||
auditAnnotationFilter cel.Filter
|
||||
messageFilter cel.Filter
|
||||
failPolicy *v1.FailurePolicyType
|
||||
authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
func NewValidator(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failPolicy *v1.FailurePolicyType, authorizer authorizer.Authorizer) Validator {
|
||||
func NewValidator(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failPolicy *v1.FailurePolicyType) Validator {
|
||||
return &validator{
|
||||
celMatcher: celMatcher,
|
||||
validationFilter: validationFilter,
|
||||
auditAnnotationFilter: auditAnnotationFilter,
|
||||
messageFilter: messageFilter,
|
||||
failPolicy: failPolicy,
|
||||
authorizer: authorizer,
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +72,8 @@ func auditAnnotationEvaluationForError(f v1.FailurePolicyType) PolicyAuditAnnota
|
||||
|
||||
// Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func (v *validator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
||||
|
||||
func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
var f v1.FailurePolicyType
|
||||
if v.failPolicy == nil {
|
||||
f = v1.Fail
|
||||
@ -81,7 +82,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
||||
}
|
||||
|
||||
if v.celMatcher != nil {
|
||||
matchResults := v.celMatcher.Match(ctx, versionedAttr, versionedParams)
|
||||
matchResults := v.celMatcher.Match(ctx, versionedAttr, versionedParams, authz)
|
||||
if matchResults.Error != nil {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
@ -100,10 +101,12 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
||||
}
|
||||
}
|
||||
|
||||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: v.authorizer}
|
||||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz}
|
||||
expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||
admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes)
|
||||
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, runtimeCELCostBudget)
|
||||
admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(matchedResource), metav1.GroupVersionKind(versionedAttr.VersionedKind))
|
||||
// Decide which fields are exposed
|
||||
ns := cel.CreateNamespaceObject(namespace)
|
||||
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, ns, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
@ -116,7 +119,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
||||
}
|
||||
}
|
||||
decisions := make([]PolicyDecision, len(evalResults))
|
||||
messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, remainingBudget)
|
||||
messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, ns, remainingBudget)
|
||||
for i, evalResult := range evalResults {
|
||||
var decision = &decisions[i]
|
||||
// TODO: move this to generics
|
||||
@ -193,7 +196,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
||||
}
|
||||
|
||||
options := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||
auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, cel.CreateAdmissionRequest(versionedAttr.Attributes), options, runtimeCELCostBudget)
|
||||
auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, admissionRequest, options, namespace, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
|
Reference in New Issue
Block a user