1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 18:15:48 +00:00

refactor: make use of handlers in engine validation (#6704)

* refactor: make use of handlers in engine validation

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* polex

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-03-28 07:47:53 +02:00 committed by GitHub
parent 54c5a4e127
commit 341ed36e54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 155 additions and 259 deletions

View file

@ -8,8 +8,12 @@ import (
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
)
// EngineContextLoader provides a function to load context entries from the various clients initialised with the engine ones
type EngineContextLoader = func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error
// EngineContextLoaderFactory provides an EngineContextLoader given a policy and rule name
type EngineContextLoaderFactory = func(policy kyvernov1.PolicyInterface, rule kyvernov1.Rule) EngineContextLoader
// Engine is the main interface to run policies against resources
type Engine interface {
// Validate applies validation rules from policy on the resource

View file

@ -69,7 +69,7 @@ func (e *engine) filterRule(
}
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(logger, ruleType, e.exceptionSelector, policyContext, rule, e.configuration)
ruleResp := e.hasPolicyExceptions(logger, ruleType, policyContext, rule)
if ruleResp != nil {
return ruleResp
}
@ -80,15 +80,14 @@ func (e *engine) filterRule(
oldResource := policyContext.OldResource()
admissionInfo := policyContext.AdmissionInfo()
ctx := policyContext.JSONContext()
excludeGroupRole := e.configuration.GetExcludedGroups()
namespaceLabels := policyContext.NamespaceLabels()
policy := policyContext.Policy()
gvk, subresource := policyContext.ResourceKind()
if err := engineutils.MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, policy.GetNamespace(), gvk, subresource); err != nil {
if err := engineutils.MatchesResourceDescription(newResource, rule, admissionInfo, namespaceLabels, policy.GetNamespace(), gvk, subresource); err != nil {
if ruleType == engineapi.Generation {
// if the oldResource matched, return "false" to delete GR for it
if err = engineutils.MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, policy.GetNamespace(), gvk, subresource); err == nil {
if err = engineutils.MatchesResourceDescription(oldResource, rule, admissionInfo, namespaceLabels, policy.GetNamespace(), gvk, subresource); err == nil {
return &engineapi.RuleResponse{
Name: rule.Name,
Type: ruleType,

View file

@ -13,7 +13,6 @@ import (
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/handlers/manifest"
"github.com/kyverno/kyverno/pkg/engine/handlers/mutation"
"github.com/kyverno/kyverno/pkg/engine/handlers/validation"
"github.com/kyverno/kyverno/pkg/engine/internal"
@ -26,16 +25,16 @@ import (
)
type engine struct {
configuration config.Configuration
client dclient.Interface
rclient registryclient.Client
contextLoader engineapi.ContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector
verifyManifestHandler handlers.Handler
mutateHandler handlers.Handler
mutateExistingHandler handlers.Handler
validateHandler handlers.Handler
validateImageHandler handlers.Handler
configuration config.Configuration
client dclient.Interface
rclient registryclient.Client
engineContextLoaderFactory engineapi.EngineContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector
validateManifestHandler handlers.Handler
mutateResourceHandler handlers.Handler
mutateExistingHandler handlers.Handler
validateResourceHandler handlers.Handler
validateImageHandler handlers.Handler
}
func NewEngine(
@ -45,19 +44,30 @@ func NewEngine(
contextLoader engineapi.ContextLoaderFactory,
exceptionSelector engineapi.PolicyExceptionSelector,
) engineapi.Engine {
e := &engine{
configuration: configuration,
client: client,
rclient: rclient,
contextLoader: contextLoader,
exceptionSelector: exceptionSelector,
verifyManifestHandler: manifest.NewHandler(client),
engineContextLoaderFactory := func(policy kyvernov1.PolicyInterface, rule kyvernov1.Rule) engineapi.EngineContextLoader {
loader := contextLoader(policy, rule)
return func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error {
return loader.Load(
ctx,
client,
rclient,
contextEntries,
jsonContext,
)
}
}
return &engine{
configuration: configuration,
client: client,
rclient: rclient,
engineContextLoaderFactory: engineContextLoaderFactory,
exceptionSelector: exceptionSelector,
validateManifestHandler: validation.NewValidateManifestHandler(client),
validateImageHandler: validation.NewValidateImageHandler(configuration),
validateResourceHandler: validation.NewValidateResourceHandler(engineContextLoaderFactory),
mutateResourceHandler: mutation.NewMutateResourceHandler(engineContextLoaderFactory),
mutateExistingHandler: mutation.NewMutateExistingHandler(client, engineContextLoaderFactory),
}
e.mutateHandler = mutation.NewHandler(configuration, e.ContextLoader)
e.mutateExistingHandler = mutation.NewMutateExistingHandler(configuration, client, e.ContextLoader)
e.validateHandler = validation.NewHandler(e.ContextLoader)
e.validateImageHandler = validation.NewValidateImageHandler(configuration, e.ContextLoader)
return e
}
func (e *engine) Validate(
@ -120,16 +130,44 @@ func (e *engine) ContextLoader(
policy kyvernov1.PolicyInterface,
rule kyvernov1.Rule,
) engineapi.EngineContextLoader {
loader := e.contextLoader(policy, rule)
return func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error {
return loader.Load(
ctx,
e.client,
e.rclient,
contextEntries,
jsonContext,
)
return e.engineContextLoaderFactory(policy, rule)
}
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
func matches(
rule kyvernov1.Rule,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
) error {
gvk, subresource := policyContext.ResourceKind()
err := engineutils.MatchesResourceDescription(
resource,
rule,
policyContext.AdmissionInfo(),
policyContext.NamespaceLabels(),
policyContext.Policy().GetNamespace(),
gvk,
subresource,
)
if err == nil {
return nil
}
oldResource := policyContext.OldResource()
if oldResource.Object != nil {
err := engineutils.MatchesResourceDescription(
policyContext.OldResource(),
rule,
policyContext.AdmissionInfo(),
policyContext.NamespaceLabels(),
policyContext.Policy().GetNamespace(),
gvk,
subresource,
)
if err == nil {
return nil
}
}
return err
}
func (e *engine) invokeRuleHandler(
@ -139,7 +177,7 @@ func (e *engine) invokeRuleHandler(
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
polexFilter func(logr.Logger, engineapi.PolicyContext, kyvernov1.Rule) *engineapi.RuleResponse,
ruleType engineapi.RuleType,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
return tracing.ChildSpan2(
ctx,
@ -147,26 +185,12 @@ func (e *engine) invokeRuleHandler(
fmt.Sprintf("RULE %s", rule.Name),
func(ctx context.Context, span trace.Span) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if resource and rule match
var excludeResource []string
if len(e.configuration.GetExcludedGroups()) > 0 {
excludeResource = e.configuration.GetExcludedGroups()
}
gvk, subresource := policyContext.ResourceKind()
if err := engineutils.MatchesResourceDescription(
resource,
rule,
policyContext.AdmissionInfo(),
excludeResource,
policyContext.NamespaceLabels(),
policyContext.Policy().GetNamespace(),
gvk,
subresource,
); err != nil {
if err := matches(rule, policyContext, resource); err != nil {
logger.V(4).Info("rule not matched", "reason", err.Error())
return resource, nil
}
// check if there's an exception
if ruleResp := polexFilter(logger, policyContext, rule); ruleResp != nil {
if ruleResp := e.hasPolicyExceptions(logger, ruleType, policyContext, rule); ruleResp != nil {
return resource, handlers.RuleResponses(ruleResp)
}
// load rule context

View file

@ -71,25 +71,23 @@ func matchesException(
// hasPolicyExceptions returns nil when there are no matching exceptions.
// A rule response is returned when an exception is matched, or there is an error.
func hasPolicyExceptions(
log logr.Logger,
func (e *engine) hasPolicyExceptions(
logger logr.Logger,
ruleType engineapi.RuleType,
selector engineapi.PolicyExceptionSelector,
ctx engineapi.PolicyContext,
rule kyvernov1.Rule,
cfg config.Configuration,
) *engineapi.RuleResponse {
// if matches, check if there is a corresponding policy exception
exception, err := matchesException(selector, ctx, rule, cfg)
exception, err := matchesException(e.exceptionSelector, ctx, rule, e.configuration)
var response *engineapi.RuleResponse
// if we found an exception
if err == nil && exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
log.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
response = internal.RuleError(&rule, ruleType, "failed to compute exception key", err)
} else {
log.V(3).Info("policy rule skipped due to policy exception", "exception", key)
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
response = internal.RuleSkip(&rule, ruleType, "rule skipped due to policy exception "+key)
response.Exception = exception
}

View file

@ -6,7 +6,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/internal"
@ -14,25 +13,22 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type handlerExisting struct {
configuration config.Configuration
type mutateExistingHandler struct {
client dclient.Interface
contextLoader func(kyvernov1.PolicyInterface, kyvernov1.Rule) engineapi.EngineContextLoader
contextLoader engineapi.EngineContextLoaderFactory
}
func NewMutateExistingHandler(
configuration config.Configuration,
client dclient.Interface,
contextLoader func(kyvernov1.PolicyInterface, kyvernov1.Rule) engineapi.EngineContextLoader,
contextLoader engineapi.EngineContextLoaderFactory,
) handlers.Handler {
return handlerExisting{
configuration: configuration,
return mutateExistingHandler{
client: client,
contextLoader: contextLoader,
}
}
func (h handlerExisting) Process(
func (h mutateExistingHandler) Process(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,

View file

@ -5,7 +5,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/mutate"
@ -13,22 +12,19 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type handler struct {
configuration config.Configuration
contextLoader func(kyvernov1.PolicyInterface, kyvernov1.Rule) engineapi.EngineContextLoader
type mutateResourceHandler struct {
contextLoader engineapi.EngineContextLoaderFactory
}
func NewHandler(
configuration config.Configuration,
contextLoader func(kyvernov1.PolicyInterface, kyvernov1.Rule) engineapi.EngineContextLoader,
func NewMutateResourceHandler(
contextLoader engineapi.EngineContextLoaderFactory,
) handlers.Handler {
return handler{
configuration: configuration,
return mutateResourceHandler{
contextLoader: contextLoader,
}
}
func (h handler) Process(
func (h mutateResourceHandler) Process(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,

View file

@ -5,7 +5,6 @@ import (
"fmt"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -18,16 +17,13 @@ import (
type validateImageHandler struct {
configuration config.Configuration
contextLoader func(kyvernov1.PolicyInterface, kyvernov1.Rule) engineapi.EngineContextLoader
}
func NewValidateImageHandler(
configuration config.Configuration,
contextLoader func(kyvernov1.PolicyInterface, kyvernov1.Rule) engineapi.EngineContextLoader,
) handlers.Handler {
return validateImageHandler{
configuration: configuration,
contextLoader: contextLoader,
}
}
@ -41,8 +37,6 @@ func (h validateImageHandler) Process(
if engineutils.IsDeleteRequest(policyContext) {
return resource, nil
}
policy := policyContext.Policy()
contextLoader := h.contextLoader(policy, rule)
matchingImages, _, err := engineutils.ExtractMatchingImages(
policyContext.NewResource(),
policyContext.JSONContext(),
@ -55,15 +49,6 @@ func (h validateImageHandler) Process(
if len(matchingImages) == 0 {
return resource, handlers.RuleResponses(internal.RuleSkip(&rule, engineapi.Validation, "image verified"))
}
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
logger.Error(err, "failed to load context")
}
return resource, handlers.RuleResponses(internal.RuleError(&rule, engineapi.Validation, "failed to load context", err))
}
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext, rule.RawAnyAllConditions)
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(&rule, engineapi.Validation, "failed to evaluate preconditions", err))

View file

@ -1,4 +1,4 @@
package manifest
package validation
import (
"context"
@ -34,17 +34,17 @@ const (
CosignEnvVariable = "COSIGN_EXPERIMENTAL"
)
type handler struct {
type validateManifestHandler struct {
client dclient.Interface
}
func NewHandler(client dclient.Interface) handlers.Handler {
return handler{
func NewValidateManifestHandler(client dclient.Interface) handlers.Handler {
return validateManifestHandler{
client: client,
}
}
func (h handler) Process(
func (h validateManifestHandler) Process(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
@ -66,7 +66,7 @@ func (h handler) Process(
return resource, handlers.RuleResponses(internal.RulePass(&rule, engineapi.Validation, reason))
}
func (h handler) verifyManifest(
func (h validateManifestHandler) verifyManifest(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
@ -167,7 +167,7 @@ func (h handler) verifyManifest(
return true, msg, nil
}
func (h handler) checkDryRunPermission(ctx context.Context, kind, namespace string) (bool, error) {
func (h validateManifestHandler) checkDryRunPermission(ctx context.Context, kind, namespace string) (bool, error) {
canI := auth.NewCanI(h.client.Discovery(), h.client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), kind, namespace, "create", "")
ok, err := canI.RunAccessCheck(ctx)
if err != nil {

View file

@ -1,4 +1,4 @@
package manifest
package validation
import (
"context"
@ -618,7 +618,7 @@ FdGxexVrR4YqO1pRViKxmD9oMu4I7K/4sM51nbH65ycB2uRiDfIdRoV/+A==
`
var (
h = handler{}
h = validateManifestHandler{}
cfg = config.NewDefaultConfiguration()
)

View file

@ -26,19 +26,19 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type handler struct {
contextLoader func(kyvernov1.PolicyInterface, kyvernov1.Rule) engineapi.EngineContextLoader
type validateResourceHandler struct {
contextLoader engineapi.EngineContextLoaderFactory
}
func NewHandler(
contextLoader func(kyvernov1.PolicyInterface, kyvernov1.Rule) engineapi.EngineContextLoader,
func NewValidateResourceHandler(
contextLoader engineapi.EngineContextLoaderFactory,
) handlers.Handler {
return handler{
return validateResourceHandler{
contextLoader: contextLoader,
}
}
func (h handler) Process(
func (h validateResourceHandler) Process(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,

View file

@ -77,12 +77,13 @@ func (e *engine) doVerifyAndPatch(
startTime := time.Now()
logger = internal.LoggerWithRule(logger, *rule)
if !matches(logger, rule, policyContext, e.configuration) {
if err := matches(*rule, policyContext, policyContext.NewResource()); err != nil {
logger.V(5).Info("resource does not match rule", "reason", err.Error())
return
}
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(logger, engineapi.ImageVerify, e.exceptionSelector, policyContext, *rule, e.configuration)
ruleResp := e.hasPolicyExceptions(logger, engineapi.ImageVerify, policyContext, *rule)
if ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
return

View file

@ -35,14 +35,11 @@ func (e *engine) mutate(
if !rule.HasMutate() {
continue
}
polexFilter := func(logger logr.Logger, policyContext engineapi.PolicyContext, rule kyvernov1.Rule) *engineapi.RuleResponse {
return hasPolicyExceptions(logger, engineapi.Validation, e.exceptionSelector, policyContext, rule, e.configuration)
}
handler := e.mutateHandler
handler := e.mutateResourceHandler
if !policyContext.AdmissionOperation() && rule.IsMutateExisting() {
handler = e.mutateExistingHandler
}
resource, ruleResp := e.invokeRuleHandler(ctx, logger, handler, policyContext, matchedResource, rule, polexFilter)
resource, ruleResp := e.invokeRuleHandler(ctx, logger, handler, policyContext, matchedResource, rule, engineapi.Mutation)
matchedResource = resource
for _, ruleResp := range ruleResp {
ruleResp := ruleResp

View file

@ -59,13 +59,6 @@ func (c *PolicyContext) Policy() kyvernov1.PolicyInterface {
}
func (c *PolicyContext) NewResource() unstructured.Unstructured {
// object, err := c.jsonContext.Query("request.object")
// if err == nil {
// if o, ok := object.(map[string]interface{}); ok {
// return unstructured.Unstructured{Object: o}
// }
// }
// return unstructured.Unstructured{}
return c.newResource
}

View file

@ -158,7 +158,6 @@ func MatchesResourceDescription(
resourceRef unstructured.Unstructured,
ruleRef kyvernov1.Rule,
admissionInfoRef kyvernov1beta1.RequestInfo,
dynamicConfig []string,
namespaceLabels map[string]string,
policyNamespace string,
gvk schema.GroupVersionKind,
@ -204,7 +203,7 @@ func MatchesResourceDescription(
if len(rule.ExcludeResources.Any) > 0 {
// exclude the object if ANY of the criteria match
for _, rer := range rule.ExcludeResources.Any {
reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, dynamicConfig, namespaceLabels, gvk, subresource)...)
reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, namespaceLabels, gvk, subresource)...)
}
} else if len(rule.ExcludeResources.All) > 0 {
// exclude the object if ALL the criteria match
@ -212,7 +211,7 @@ func MatchesResourceDescription(
for _, rer := range rule.ExcludeResources.All {
// we got no errors inplying a resource did NOT exclude it
// "matchesResourceDescriptionExcludeHelper" returns errors if resource is excluded by a filter
if len(matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, dynamicConfig, namespaceLabels, gvk, subresource)) == 0 {
if len(matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, namespaceLabels, gvk, subresource)) == 0 {
excludedByAll = false
break
}
@ -222,7 +221,7 @@ func MatchesResourceDescription(
}
} else {
rer := kyvernov1.ResourceFilter{UserInfo: rule.ExcludeResources.UserInfo, ResourceDescription: rule.ExcludeResources.ResourceDescription}
reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, dynamicConfig, namespaceLabels, gvk, subresource)...)
reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, namespaceLabels, gvk, subresource)...)
}
// creating final error
@ -269,7 +268,6 @@ func matchesResourceDescriptionExcludeHelper(
rer kyvernov1.ResourceFilter,
admissionInfo kyvernov1beta1.RequestInfo,
resource unstructured.Unstructured,
dynamicConfig []string,
namespaceLabels map[string]string,
gvk schema.GroupVersionKind,
subresource string,

View file

@ -906,7 +906,7 @@ func TestMatchesResourceDescription(t *testing.T) {
resource, _ := kubeutils.BytesToUnstructured(tc.Resource)
for _, rule := range autogen.ComputeRules(&policy) {
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil, "", resource.GroupVersionKind(), "")
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, nil, "", resource.GroupVersionKind(), "")
if err != nil {
if !tc.areErrorsExpected {
t.Errorf("Testcase %d Unexpected error: %v\nmsg: %s", i+1, err, tc.Description)
@ -1811,7 +1811,7 @@ func TestMatchesResourceDescription_GenerateName(t *testing.T) {
resource, _ := kubeutils.BytesToUnstructured(tc.Resource)
for _, rule := range autogen.ComputeRules(&policy) {
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil, "", resource.GroupVersionKind(), "")
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, nil, "", resource.GroupVersionKind(), "")
if err != nil {
if !tc.areErrorsExpected {
t.Errorf("Testcase %d Unexpected error: %v\nmsg: %s", i+1, err, tc.Description)
@ -1878,7 +1878,7 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
}
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1967,15 +1967,6 @@ func TestResourceDescriptionMatch_ExcludeDefaultGroups(t *testing.T) {
ExcludeResources: v1.MatchResources{},
}
// the following groups are added by default to ensure that these groups or users are always excluded
// these should not affect the rule in our case, we expect our rule to NOT match with the nginx pod.
dynamicConfig := []string{
"system:serviceaccounts:kube-system",
"system:nodes",
"system:kube-scheduler",
}
// this is the request info that was also passed with the mocked pod
requestInfo := v1beta1.RequestInfo{
AdmissionUserInfo: authenticationv1.UserInfo{
@ -1990,7 +1981,7 @@ func TestResourceDescriptionMatch_ExcludeDefaultGroups(t *testing.T) {
}
// First test: confirm that this above rule produces errors (and raise an error if err == nil)
if err := MatchesResourceDescription(*resource, rule, requestInfo, dynamicConfig, nil, "", resource.GroupVersionKind(), ""); err == nil {
if err := MatchesResourceDescription(*resource, rule, requestInfo, nil, "", resource.GroupVersionKind(), ""); err == nil {
t.Error("Testcase was expected to fail, but err was nil")
}
@ -2010,7 +2001,7 @@ func TestResourceDescriptionMatch_ExcludeDefaultGroups(t *testing.T) {
}
// Second test: confirm that matching this rule does not create any errors (and raise if err != nil)
if err := MatchesResourceDescription(*resource, rule2, requestInfo, dynamicConfig, nil, "", resource.GroupVersionKind(), ""); err != nil {
if err := MatchesResourceDescription(*resource, rule2, requestInfo, nil, "", resource.GroupVersionKind(), ""); err != nil {
t.Errorf("Testcase was expected to not fail, but err was %s", err)
}
@ -2024,7 +2015,7 @@ func TestResourceDescriptionMatch_ExcludeDefaultGroups(t *testing.T) {
}}
// Third test: confirm that now the custom exclude-snippet should run in CheckSubjects() and that should result in this rule failing (raise if err == nil for that reason)
if err := MatchesResourceDescription(*resource, rule2, requestInfo, dynamicConfig, nil, "", resource.GroupVersionKind(), ""); err == nil {
if err := MatchesResourceDescription(*resource, rule2, requestInfo, nil, "", resource.GroupVersionKind(), ""); err == nil {
t.Error("Testcase was expected to fail, but err was nil #1!")
}
}
@ -2083,7 +2074,7 @@ func TestResourceDescriptionMatch_Name(t *testing.T) {
}
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -2141,7 +2132,7 @@ func TestResourceDescriptionMatch_GenerateName(t *testing.T) {
}
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -2200,7 +2191,7 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
}
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -2258,7 +2249,7 @@ func TestResourceDescriptionMatch_GenerateName_Regex(t *testing.T) {
}
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -2325,7 +2316,7 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
}
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -2393,7 +2384,7 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
}
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, nil, "", resource.GroupVersionKind(), ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -2474,7 +2465,7 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
ExcludeResources: v1.MatchResources{ResourceDescription: resourceDescriptionExclude},
}
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err == nil {
if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, nil, "", resource.GroupVersionKind(), ""); err == nil {
t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail")
}
}

View file

@ -2,19 +2,14 @@ package engine
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/internal"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/tracing"
"go.opentelemetry.io/otel/trace"
)
func (e *engine) validate(
@ -41,117 +36,36 @@ func (e *engine) validateResource(
policyContext.JSONContext().Checkpoint()
defer policyContext.JSONContext().Restore()
rules := autogen.ComputeRules(policyContext.Policy())
matchCount := 0
applyRules := policyContext.Policy().GetSpec().GetApplyRules()
for i := range rules {
rule := &rules[i]
logger := internal.LoggerWithRule(logger, rules[i])
logger.V(3).Info("processing validation rule", "matchCount", matchCount)
policyContext.JSONContext().Reset()
for _, rule := range autogen.ComputeRules(policyContext.Policy()) {
logger := internal.LoggerWithRule(logger, rule)
startTime := time.Now()
ruleResp := tracing.ChildSpan1(
ctx,
"pkg/engine",
fmt.Sprintf("RULE %s", rule.Name),
func(ctx context.Context, span trace.Span) []engineapi.RuleResponse {
hasValidate := rule.HasValidate()
hasValidateImage := rule.HasImagesValidationChecks()
hasYAMLSignatureVerify := rule.HasYAMLSignatureVerify()
if !hasValidate && !hasValidateImage {
return nil
}
if !matches(logger, rule, policyContext, e.configuration) {
return nil
}
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(logger, engineapi.Validation, e.exceptionSelector, policyContext, *rule, e.configuration)
if ruleResp != nil {
return handlers.RuleResponses(ruleResp)
}
policyContext.JSONContext().Reset()
if hasValidate && !hasYAMLSignatureVerify {
_, rr := e.validateHandler.Process(
ctx,
logger,
policyContext,
policyContext.NewResource(),
*rule,
)
return rr
} else if hasValidateImage {
_, rr := e.validateImageHandler.Process(
ctx,
logger,
policyContext,
policyContext.NewResource(),
*rule,
)
return rr
} else if hasYAMLSignatureVerify {
_, rr := e.verifyManifestHandler.Process(
ctx,
logger,
policyContext,
policyContext.NewResource(),
*rule,
)
return rr
}
return nil
},
)
for _, ruleResp := range ruleResp {
ruleResp := ruleResp
internal.AddRuleResponse(resp, &ruleResp, startTime)
logger.V(4).Info("finished processing rule", "processingTime", ruleResp.Stats.ProcessingTime.String())
hasValidate := rule.HasValidate()
hasValidateImage := rule.HasImagesValidationChecks()
hasYAMLSignatureVerify := rule.HasYAMLSignatureVerify()
if !hasValidate && !hasValidateImage {
continue
}
var handler handlers.Handler
if hasValidate && !hasYAMLSignatureVerify {
handler = e.validateResourceHandler
} else if hasValidateImage {
handler = e.validateImageHandler
} else if hasYAMLSignatureVerify {
handler = e.validateManifestHandler
}
if handler != nil {
_, ruleResp := e.invokeRuleHandler(ctx, logger, handler, policyContext, policyContext.NewResource(), rule, engineapi.Validation)
for _, ruleResp := range ruleResp {
ruleResp := ruleResp
internal.AddRuleResponse(resp, &ruleResp, startTime)
logger.V(4).Info("finished processing rule", "processingTime", ruleResp.Stats.ProcessingTime.String())
}
}
if applyRules == kyvernov1.ApplyOne && resp.Stats.RulesAppliedCount > 0 {
break
}
}
return resp
}
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
func matches(
logger logr.Logger,
rule *kyvernov1.Rule,
ctx engineapi.PolicyContext,
cfg config.Configuration,
) bool {
gvk, subresource := ctx.ResourceKind()
err := engineutils.MatchesResourceDescription(
ctx.NewResource(),
*rule,
ctx.AdmissionInfo(),
cfg.GetExcludedGroups(),
ctx.NamespaceLabels(),
"",
gvk,
subresource,
)
if err == nil {
return true
}
oldResource := ctx.OldResource()
if oldResource.Object != nil {
err := engineutils.MatchesResourceDescription(
ctx.OldResource(),
*rule,
ctx.AdmissionInfo(),
cfg.GetExcludedGroups(),
ctx.NamespaceLabels(),
"",
gvk,
subresource,
)
if err == nil {
return true
}
}
logger.V(5).Info("resource does not match rule", "reason", err.Error())
return false
}