mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-10 18:06:55 +00:00
183 lines
6.1 KiB
Go
183 lines
6.1 KiB
Go
|
package validatingadmissionpolicy
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||
|
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||
|
"golang.org/x/text/cases"
|
||
|
"golang.org/x/text/language"
|
||
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||
|
"k8s.io/apiserver/pkg/admission"
|
||
|
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||
|
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
||
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||
|
)
|
||
|
|
||
|
func GetKinds(policy v1alpha1.ValidatingAdmissionPolicy) []string {
|
||
|
var kindList []string
|
||
|
|
||
|
matchResources := policy.Spec.MatchConstraints
|
||
|
for _, rule := range matchResources.ResourceRules {
|
||
|
group := rule.APIGroups[0]
|
||
|
version := rule.APIVersions[0]
|
||
|
for _, resource := range rule.Resources {
|
||
|
isSubresource := kubeutils.IsSubresource(resource)
|
||
|
if isSubresource {
|
||
|
parts := strings.Split(resource, "/")
|
||
|
|
||
|
kind := cases.Title(language.English, cases.NoLower).String(parts[0])
|
||
|
kind, _ = strings.CutSuffix(kind, "s")
|
||
|
subresource := parts[1]
|
||
|
|
||
|
if group == "" {
|
||
|
kindList = append(kindList, strings.Join([]string{version, kind, subresource}, "/"))
|
||
|
} else {
|
||
|
kindList = append(kindList, strings.Join([]string{group, version, kind, subresource}, "/"))
|
||
|
}
|
||
|
} else {
|
||
|
resource = cases.Title(language.English, cases.NoLower).String(resource)
|
||
|
resource, _ = strings.CutSuffix(resource, "s")
|
||
|
kind := resource
|
||
|
|
||
|
if group == "" {
|
||
|
kindList = append(kindList, strings.Join([]string{version, kind}, "/"))
|
||
|
} else {
|
||
|
kindList = append(kindList, strings.Join([]string{group, version, kind}, "/"))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return kindList
|
||
|
}
|
||
|
|
||
|
func Validate(policy v1alpha1.ValidatingAdmissionPolicy, resource unstructured.Unstructured) engineapi.EngineResponse {
|
||
|
resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
|
||
|
logger.V(3).Info("applying policy on resource", "policy", policy.GetName(), "resource", resPath)
|
||
|
|
||
|
startTime := time.Now()
|
||
|
|
||
|
var expressions, messageExpressions, matchExpressions, auditExpressions []cel.ExpressionAccessor
|
||
|
|
||
|
validations := policy.Spec.Validations
|
||
|
matchConditions := policy.Spec.MatchConditions
|
||
|
auditAnnotations := policy.Spec.AuditAnnotations
|
||
|
|
||
|
hasParam := policy.Spec.ParamKind != nil
|
||
|
|
||
|
var failPolicy admissionregistrationv1.FailurePolicyType
|
||
|
if policy.Spec.FailurePolicy == nil {
|
||
|
failPolicy = admissionregistrationv1.Fail
|
||
|
} else {
|
||
|
failPolicy = admissionregistrationv1.FailurePolicyType(*policy.Spec.FailurePolicy)
|
||
|
}
|
||
|
|
||
|
var matchPolicy v1alpha1.MatchPolicyType
|
||
|
if policy.Spec.MatchConstraints.MatchPolicy == nil {
|
||
|
matchPolicy = v1alpha1.Equivalent
|
||
|
} else {
|
||
|
matchPolicy = *policy.Spec.MatchConstraints.MatchPolicy
|
||
|
}
|
||
|
|
||
|
for _, cel := range validations {
|
||
|
condition := &validatingadmissionpolicy.ValidationCondition{
|
||
|
Expression: cel.Expression,
|
||
|
Message: cel.Message,
|
||
|
}
|
||
|
messageCondition := &validatingadmissionpolicy.MessageExpressionCondition{
|
||
|
MessageExpression: cel.MessageExpression,
|
||
|
}
|
||
|
expressions = append(expressions, condition)
|
||
|
messageExpressions = append(messageExpressions, messageCondition)
|
||
|
}
|
||
|
|
||
|
for _, expression := range matchConditions {
|
||
|
condition := &matchconditions.MatchCondition{
|
||
|
Name: expression.Name,
|
||
|
Expression: expression.Expression,
|
||
|
}
|
||
|
matchExpressions = append(matchExpressions, condition)
|
||
|
}
|
||
|
|
||
|
for _, auditAnnotation := range auditAnnotations {
|
||
|
auditCondition := &validatingadmissionpolicy.AuditAnnotationCondition{
|
||
|
Key: auditAnnotation.Key,
|
||
|
ValueExpression: auditAnnotation.ValueExpression,
|
||
|
}
|
||
|
auditExpressions = append(auditExpressions, auditCondition)
|
||
|
}
|
||
|
|
||
|
filterCompiler := cel.NewFilterCompiler()
|
||
|
filter := filterCompiler.Compile(
|
||
|
expressions,
|
||
|
cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false},
|
||
|
celconfig.PerCallLimit,
|
||
|
)
|
||
|
messageExpressionfilter := filterCompiler.Compile(
|
||
|
messageExpressions,
|
||
|
cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false},
|
||
|
celconfig.PerCallLimit,
|
||
|
)
|
||
|
auditAnnotationFilter := filterCompiler.Compile(
|
||
|
auditExpressions,
|
||
|
cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false},
|
||
|
celconfig.PerCallLimit,
|
||
|
)
|
||
|
matchConditionFilter := filterCompiler.Compile(
|
||
|
matchExpressions,
|
||
|
cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false},
|
||
|
celconfig.PerCallLimit,
|
||
|
)
|
||
|
|
||
|
newMatcher := matchconditions.NewMatcher(matchConditionFilter, nil, &failPolicy, string(matchPolicy), "")
|
||
|
validator := validatingadmissionpolicy.NewValidator(filter, newMatcher, auditAnnotationFilter, messageExpressionfilter, nil, nil)
|
||
|
|
||
|
admissionAttributes := admission.NewAttributesRecord(
|
||
|
resource.DeepCopyObject(),
|
||
|
nil, resource.GroupVersionKind(),
|
||
|
resource.GetNamespace(),
|
||
|
resource.GetName(),
|
||
|
schema.GroupVersionResource{},
|
||
|
"",
|
||
|
admission.Create,
|
||
|
nil,
|
||
|
false,
|
||
|
nil,
|
||
|
)
|
||
|
versionedAttr, _ := admission.NewVersionedAttributes(admissionAttributes, admissionAttributes.GetKind(), nil)
|
||
|
validateResult := validator.Validate(context.TODO(), versionedAttr, nil, celconfig.RuntimeCELCostBudget)
|
||
|
|
||
|
engineResponse := engineapi.NewEngineResponseWithValidatingAdmissionPolicy(resource, policy, nil)
|
||
|
policyResp := engineapi.NewPolicyResponse()
|
||
|
var ruleResp *engineapi.RuleResponse
|
||
|
isPass := true
|
||
|
|
||
|
for _, policyDecision := range validateResult.Decisions {
|
||
|
if policyDecision.Evaluation == validatingadmissionpolicy.EvalError {
|
||
|
isPass = false
|
||
|
ruleResp = engineapi.RuleError(policy.GetName(), engineapi.Validation, policyDecision.Message, nil)
|
||
|
break
|
||
|
} else if policyDecision.Action == validatingadmissionpolicy.ActionDeny {
|
||
|
isPass = false
|
||
|
ruleResp = engineapi.RuleFail(policy.GetName(), engineapi.Validation, policyDecision.Message)
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if isPass {
|
||
|
ruleResp = engineapi.RulePass(policy.GetName(), engineapi.Validation, "")
|
||
|
}
|
||
|
policyResp.Add(engineapi.NewExecutionStats(startTime, time.Now()), *ruleResp)
|
||
|
engineResponse = engineResponse.WithPolicyResponse(policyResp)
|
||
|
|
||
|
return engineResponse
|
||
|
}
|