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

refactor: introduce internal engine package (#6241)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-02-07 05:30:15 +01:00 committed by GitHub
parent 60cf8afff9
commit dbddc83cb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 173 additions and 149 deletions

View file

@ -7,6 +7,7 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/logging"
@ -131,7 +132,7 @@ func (e *engine) filterRule(
policyContext.JSONContext().Checkpoint()
defer policyContext.JSONContext().Restore()
if err := LoadContext(context.TODO(), e.contextLoader, rule.Context, policyContext, rule.Name); err != nil {
if err := internal.LoadContext(context.TODO(), e.contextLoader, rule.Context, policyContext, rule.Name); err != nil {
logger.V(4).Info("cannot add external data to the context", "reason", err.Error())
return nil
}

View file

@ -17,6 +17,7 @@ import (
"github.com/kyverno/kyverno/pkg/cosign"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/registryclient"
@ -43,7 +44,7 @@ func (e *engine) verifyAndPatchImages(
startTime := time.Now()
defer func() {
buildResponse(policyContext, resp, startTime)
internal.BuildResponse(policyContext, resp, startTime)
logger.V(4).Info("processed image verification rules",
"time", resp.PolicyResponse.ProcessingTime.String(),
"applied", resp.PolicyResponse.RulesAppliedCount, "successful", resp.IsSuccessful())
@ -100,7 +101,7 @@ func (e *engine) verifyAndPatchImages(
}
policyContext.JSONContext().Restore()
if err := LoadContext(ctx, e.contextLoader, rule.Context, policyContext, rule.Name); err != nil {
if err := internal.LoadContext(ctx, e.contextLoader, rule.Context, policyContext, rule.Name); err != nil {
appendResponse(resp, rule, fmt.Sprintf("failed to load context: %s", err.Error()), engineapi.RuleStatusError)
return
}
@ -172,7 +173,7 @@ func (e *engine) extractMatchingImages(policyContext engineapi.PolicyContext, ru
}
func appendResponse(resp *engineapi.EngineResponse, rule *kyvernov1.Rule, msg string, status engineapi.RuleStatus) {
rr := ruleResponse(*rule, engineapi.ImageVerify, msg, status)
rr := internal.RuleResponse(*rule, engineapi.ImageVerify, msg, status)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *rr)
incrementErrorCount(resp)
}
@ -219,7 +220,7 @@ func (iv *imageVerifier) verify(ctx context.Context, imageVerify kyvernov1.Image
if hasImageVerifiedAnnotationChanged(iv.policyContext, iv.logger) {
msg := engineapi.ImageVerifyAnnotationKey + " annotation cannot be changed"
iv.logger.Info("image verification error", "reason", msg)
ruleResp := ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail)
ruleResp := internal.RuleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail)
iv.resp.PolicyResponse.Rules = append(iv.resp.PolicyResponse.Rules, *ruleResp)
incrementAppliedCount(iv.resp)
continue
@ -243,10 +244,10 @@ func (iv *imageVerifier) verify(ctx context.Context, imageVerify kyvernov1.Image
if imageVerify.MutateDigest {
patch, retrievedDigest, err := iv.handleMutateDigest(ctx, digest, imageInfo)
if err != nil {
ruleResp = ruleError(iv.rule, engineapi.ImageVerify, "failed to update digest", err)
ruleResp = internal.RuleError(iv.rule, engineapi.ImageVerify, "failed to update digest", err)
} else if patch != nil {
if ruleResp == nil {
ruleResp = ruleResponse(*iv.rule, engineapi.ImageVerify, "mutated image digest", engineapi.RuleStatusPass)
ruleResp = internal.RuleResponse(*iv.rule, engineapi.ImageVerify, "mutated image digest", engineapi.RuleStatusPass)
}
ruleResp.Patches = append(ruleResp.Patches, patch)
@ -336,7 +337,7 @@ func (iv *imageVerifier) verifyImage(
if err := iv.policyContext.JSONContext().AddImageInfo(imageInfo, cfg); err != nil {
iv.logger.Error(err, "failed to add image to context")
msg := fmt.Sprintf("failed to add image to context %s: %s", image, err.Error())
return ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusError), ""
return internal.RuleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusError), ""
}
if len(imageVerify.Attestors) > 0 {
@ -391,11 +392,11 @@ func (iv *imageVerifier) verifyAttestors(
}
if cosignResponse == nil {
return ruleError(iv.rule, engineapi.ImageVerify, "invalid response", fmt.Errorf("nil")), nil
return internal.RuleError(iv.rule, engineapi.ImageVerify, "invalid response", fmt.Errorf("nil")), nil
}
msg := fmt.Sprintf("verified image signatures for %s", image)
return ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusPass), cosignResponse
return internal.RuleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusPass), cosignResponse
}
// handle registry network errors as a rule error (instead of a policy failure)
@ -403,10 +404,10 @@ func (iv *imageVerifier) handleRegistryErrors(image string, err error) *engineap
msg := fmt.Sprintf("failed to verify image %s: %s", image, err.Error())
var netErr *net.OpError
if errors.As(err, &netErr) {
return ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusError)
return internal.RuleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusError)
}
return ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail)
return internal.RuleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail)
}
func (iv *imageVerifier) verifyAttestations(
@ -420,7 +421,7 @@ func (iv *imageVerifier) verifyAttestations(
path := fmt.Sprintf(".attestations[%d]", i)
if attestation.PredicateType == "" {
return ruleResponse(*iv.rule, engineapi.ImageVerify, path+": missing predicateType", engineapi.RuleStatusFail), ""
return internal.RuleResponse(*iv.rule, engineapi.ImageVerify, path+": missing predicateType", engineapi.RuleStatusFail), ""
}
if len(attestation.Attestors) == 0 {
@ -450,7 +451,7 @@ func (iv *imageVerifier) verifyAttestations(
attestationError = iv.verifyAttestation(cosignResp.Statements, attestation, imageInfo)
if attestationError != nil {
attestationError = fmt.Errorf("%s: %w", entryPath+subPath, attestationError)
return ruleResponse(*iv.rule, engineapi.ImageVerify, attestationError.Error(), engineapi.RuleStatusFail), ""
return internal.RuleResponse(*iv.rule, engineapi.ImageVerify, attestationError.Error(), engineapi.RuleStatusFail), ""
}
verifiedCount++
@ -462,7 +463,7 @@ func (iv *imageVerifier) verifyAttestations(
if verifiedCount < requiredCount {
msg := fmt.Sprintf("image attestations verification failed, verifiedCount: %v, requiredCount: %v", verifiedCount, requiredCount)
return ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail), ""
return internal.RuleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusFail), ""
}
}
@ -471,7 +472,7 @@ func (iv *imageVerifier) verifyAttestations(
msg := fmt.Sprintf("verified image attestations for %s", image)
iv.logger.V(2).Info(msg)
return ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusPass), imageInfo.Digest
return internal.RuleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusPass), imageInfo.Digest
}
func (iv *imageVerifier) verifyAttestorSet(

View file

@ -9,6 +9,7 @@ import (
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/internal"
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -26,24 +27,24 @@ func (e *engine) processImageValidationRule(
log = log.WithValues("rule", rule.Name)
matchingImages, _, err := e.extractMatchingImages(enginectx, rule)
if err != nil {
return ruleResponse(*rule, engineapi.Validation, err.Error(), engineapi.RuleStatusError)
return internal.RuleResponse(*rule, engineapi.Validation, err.Error(), engineapi.RuleStatusError)
}
if len(matchingImages) == 0 {
return ruleResponse(*rule, engineapi.Validation, "image verified", engineapi.RuleStatusSkip)
return internal.RuleResponse(*rule, engineapi.Validation, "image verified", engineapi.RuleStatusSkip)
}
if err := LoadContext(ctx, e.contextLoader, rule.Context, enginectx, rule.Name); err != nil {
if err := internal.LoadContext(ctx, e.contextLoader, rule.Context, enginectx, rule.Name); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
log.V(3).Info("failed to load context", "reason", err.Error())
} else {
log.Error(err, "failed to load context")
}
return ruleError(rule, engineapi.Validation, "failed to load context", err)
return internal.RuleError(rule, engineapi.Validation, "failed to load context", err)
}
preconditionsPassed, err := checkPreconditions(log, enginectx, rule.RawAnyAllConditions)
if err != nil {
return ruleError(rule, engineapi.Validation, "failed to evaluate preconditions", err)
return internal.RuleError(rule, engineapi.Validation, "failed to evaluate preconditions", err)
}
if !preconditionsPassed {
@ -51,7 +52,7 @@ func (e *engine) processImageValidationRule(
return nil
}
return ruleResponse(*rule, engineapi.Validation, "preconditions not met", engineapi.RuleStatusSkip)
return internal.RuleResponse(*rule, engineapi.Validation, "preconditions not met", engineapi.RuleStatusSkip)
}
for _, v := range rule.VerifyImages {
@ -68,14 +69,14 @@ func (e *engine) processImageValidationRule(
log.V(4).Info("validating image", "image", image)
if err := validateImage(enginectx, imageVerify, name, imageInfo, log); err != nil {
return ruleResponse(*rule, engineapi.ImageVerify, err.Error(), engineapi.RuleStatusFail)
return internal.RuleResponse(*rule, engineapi.ImageVerify, err.Error(), engineapi.RuleStatusFail)
}
}
}
}
log.V(4).Info("validated image", "rule", rule.Name)
return ruleResponse(*rule, engineapi.Validation, "image verified", engineapi.RuleStatusPass)
return internal.RuleResponse(*rule, engineapi.Validation, "image verified", engineapi.RuleStatusPass)
}
func validateImage(ctx engineapi.PolicyContext, imageVerify *kyvernov1.ImageVerification, name string, imageInfo apiutils.ImageInfo, log logr.Logger) error {

View file

@ -0,0 +1,12 @@
package internal
import (
"context"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
)
func LoadContext(ctx context.Context, factory engineapi.ContextLoaderFactory, contextEntries []kyvernov1.ContextEntry, pContext engineapi.PolicyContext, ruleName string) error {
return factory(pContext, ruleName).Load(ctx, contextEntries, pContext.JSONContext())
}

View file

@ -0,0 +1,22 @@
package internal
import (
"reflect"
"github.com/go-logr/logr"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/logging"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func BuildLogger(ctx engineapi.PolicyContext) logr.Logger {
logger := logging.WithName("EngineValidate").WithValues("policy", ctx.Policy().GetName())
newResource := ctx.NewResource()
oldResource := ctx.OldResource()
if reflect.DeepEqual(newResource, unstructured.Unstructured{}) {
logger = logger.WithValues("kind", oldResource.GetKind(), "namespace", oldResource.GetNamespace(), "name", oldResource.GetName())
} else {
logger = logger.WithValues("kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName())
}
return logger
}

View file

@ -0,0 +1,57 @@
package internal
import (
"fmt"
"reflect"
"time"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func RuleError(rule *kyvernov1.Rule, ruleType engineapi.RuleType, msg string, err error) *engineapi.RuleResponse {
msg = fmt.Sprintf("%s: %s", msg, err.Error())
return RuleResponse(*rule, ruleType, msg, engineapi.RuleStatusError)
}
func RuleResponse(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string, status engineapi.RuleStatus) *engineapi.RuleResponse {
resp := &engineapi.RuleResponse{
Name: rule.Name,
Type: ruleType,
Message: msg,
Status: status,
}
return resp
}
func BuildResponse(ctx engineapi.PolicyContext, resp *engineapi.EngineResponse, startTime time.Time) *engineapi.EngineResponse {
resp.NamespaceLabels = ctx.NamespaceLabels()
if reflect.DeepEqual(resp, engineapi.EngineResponse{}) {
return resp
}
if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) {
// for delete requests patched resource will be oldResource since newResource is empty
resource := ctx.NewResource()
if reflect.DeepEqual(resource, unstructured.Unstructured{}) {
resource = ctx.OldResource()
}
resp.PatchedResource = resource
}
policy := ctx.Policy()
resp.Policy = policy
resp.PolicyResponse.Policy.Name = policy.GetName()
resp.PolicyResponse.Policy.Namespace = policy.GetNamespace()
resp.PolicyResponse.Resource.Name = resp.PatchedResource.GetName()
resp.PolicyResponse.Resource.Namespace = resp.PatchedResource.GetNamespace()
resp.PolicyResponse.Resource.Kind = resp.PatchedResource.GetKind()
resp.PolicyResponse.Resource.APIVersion = resp.PatchedResource.GetAPIVersion()
resp.PolicyResponse.ValidationFailureAction = policy.GetSpec().ValidationFailureAction
for _, v := range policy.GetSpec().ValidationFailureActionOverrides {
newOverrides := engineapi.ValidationFailureActionOverride{Action: v.Action, Namespaces: v.Namespaces, NamespaceSelector: v.NamespaceSelector}
resp.PolicyResponse.ValidationFailureActionOverrides = append(resp.PolicyResponse.ValidationFailureActionOverrides, newOverrides)
}
resp.PolicyResponse.ProcessingTime = time.Since(startTime)
resp.PolicyResponse.Timestamp = startTime.Unix()
return resp
}

View file

@ -122,10 +122,6 @@ func (l *mockContextLoader) Load(ctx context.Context, contextEntries []kyvernov1
return nil
}
func LoadContext(ctx context.Context, factory engineapi.ContextLoaderFactory, contextEntries []kyvernov1.ContextEntry, pContext engineapi.PolicyContext, ruleName string) error {
return factory(pContext, ruleName).Load(ctx, contextEntries, pContext.JSONContext())
}
func loadVariable(logger logr.Logger, entry kyvernov1.ContextEntry, ctx enginecontext.Interface) (err error) {
path := ""
if entry.Variable.JMESPath != "" {

View file

@ -20,6 +20,7 @@ import (
"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/internal"
"github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest"
"go.uber.org/multierr"
admissionv1 "k8s.io/api/admission/v1"
@ -46,13 +47,13 @@ func handleVerifyManifest(ctx engineapi.PolicyContext, rule *kyvernov1.Rule, log
verified, reason, err := verifyManifest(ctx, *rule.Validation.Manifests, logger)
if err != nil {
logger.V(3).Info("verifyManifest return err", "error", err.Error())
return ruleError(rule, engineapi.Validation, "error occurred during manifest verification", err)
return internal.RuleError(rule, engineapi.Validation, "error occurred during manifest verification", err)
}
logger.V(3).Info("verifyManifest result", "verified", strconv.FormatBool(verified), "reason", reason)
if !verified {
return ruleResponse(*rule, engineapi.Validation, reason, engineapi.RuleStatusFail)
return internal.RuleResponse(*rule, engineapi.Validation, reason, engineapi.RuleStatusFail)
}
return ruleResponse(*rule, engineapi.Validation, reason, engineapi.RuleStatusPass)
return internal.RuleResponse(*rule, engineapi.Validation, reason, engineapi.RuleStatusPass)
}
func verifyManifest(policyContext engineapi.PolicyContext, verifyRule kyvernov1.Manifests, logger logr.Logger) (bool, string, error) {

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"testing"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"gotest.tools/assert"
v1 "k8s.io/api/admission/v1"
@ -626,7 +627,7 @@ func Test_VerifyManifest_SignedYAML(t *testing.T) {
},
},
})
logger := buildLogger(policyContext)
logger := logr.Discard()
verified, _, err := verifyManifest(policyContext, verifyRule, logger)
assert.NilError(t, err)
assert.Equal(t, verified, true)
@ -648,7 +649,7 @@ func Test_VerifyManifest_UnsignedYAML(t *testing.T) {
},
},
})
logger := buildLogger(policyContext)
logger := logr.Discard()
verified, _, err := verifyManifest(policyContext, verifyRule, logger)
assert.NilError(t, err)
assert.Equal(t, verified, false)
@ -670,7 +671,7 @@ func Test_VerifyManifest_InvalidYAML(t *testing.T) {
},
},
})
logger := buildLogger(policyContext)
logger := logr.Discard()
verified, _, err := verifyManifest(policyContext, verifyRule, logger)
assert.NilError(t, err)
assert.Equal(t, verified, false)
@ -697,7 +698,7 @@ func Test_VerifyManifest_MustAll_InvalidYAML(t *testing.T) {
},
},
})
logger := buildLogger(policyContext)
logger := logr.Discard()
verified, _, err := verifyManifest(policyContext, verifyRule, logger)
errMsg := `.attestors[0].entries[1].keys: failed to verify signature: verification failed for 1 signature. all trials: ["[publickey 1/1] [signature 1/1] error: cosign.VerifyBlobCmd() returned an error: invalid signature when validating ASN.1 encoded signature"]`
assert.Error(t, err, errMsg)
@ -730,7 +731,7 @@ func Test_VerifyManifest_MustAll_ValidYAML(t *testing.T) {
},
},
})
logger := buildLogger(policyContext)
logger := logr.Discard()
verified, _, err := verifyManifest(policyContext, verifyRule, logger)
assert.NilError(t, err)
assert.Equal(t, verified, true)
@ -759,7 +760,7 @@ func Test_VerifyManifest_AtLeastOne(t *testing.T) {
},
},
})
logger := buildLogger(policyContext)
logger := logr.Discard()
verified, _, err := verifyManifest(policyContext, verifyRule, logger)
assert.NilError(t, err)
assert.Equal(t, verified, true)

View file

@ -11,6 +11,7 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/mutate"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/tracing"
@ -91,7 +92,7 @@ func (e *engine) mutate(
logger.Error(err, "failed to query resource object")
}
if err := LoadContext(ctx, e.contextLoader, rule.Context, policyContext, rule.Name); err != nil {
if err := internal.LoadContext(ctx, e.contextLoader, rule.Context, policyContext, rule.Name); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
@ -105,7 +106,7 @@ func (e *engine) mutate(
if !policyContext.AdmissionOperation() && rule.IsMutateExisting() {
targets, err := loadTargets(ruleCopy.Mutation.Targets, policyContext, logger)
if err != nil {
rr := ruleResponse(rule, engineapi.Mutation, err.Error(), engineapi.RuleStatusError)
rr := internal.RuleResponse(rule, engineapi.Mutation, err.Error(), engineapi.RuleStatusError)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *rr)
} else {
patchedResources = append(patchedResources, targets...)
@ -213,7 +214,7 @@ func (f *forEachMutator) mutateForEach(ctx context.Context) *mutate.Response {
allPatches := make([][]byte, 0)
for _, foreach := range f.foreach {
if err := LoadContext(ctx, f.contextLoader, f.rule.Context, f.policyContext, f.rule.Name); err != nil {
if err := internal.LoadContext(ctx, f.contextLoader, f.rule.Context, f.policyContext, f.rule.Name); err != nil {
f.log.Error(err, "failed to load context")
return mutate.NewErrorResponse("failed to load context", err)
}
@ -278,7 +279,7 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
return mutate.NewErrorResponse(fmt.Sprintf("failed to add element to mutate.foreach[%d].context", index), err)
}
if err := LoadContext(ctx, f.contextLoader, foreach.Context, policyContext, f.rule.Name); err != nil {
if err := internal.LoadContext(ctx, f.contextLoader, foreach.Context, policyContext, f.rule.Name); err != nil {
return mutate.NewErrorResponse(fmt.Sprintf("failed to load to mutate.foreach[%d].context", index), err)
}
@ -328,7 +329,7 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
}
func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, info resourceInfo) *engineapi.RuleResponse {
resp := ruleResponse(*rule, engineapi.Mutation, mutateResp.Message, mutateResp.Status)
resp := internal.RuleResponse(*rule, engineapi.Mutation, mutateResp.Message, mutateResp.Status)
if resp.Status == engineapi.RuleStatusPass {
resp.Patches = mutateResp.Patches
resp.Message = buildSuccessMessage(mutateResp.PatchedResource)

View file

@ -353,21 +353,6 @@ func evaluateList(jmesPath string, ctx context.EvalInterface) ([]interface{}, er
return l, nil
}
func ruleError(rule *kyvernov1.Rule, ruleType engineapi.RuleType, msg string, err error) *engineapi.RuleResponse {
msg = fmt.Sprintf("%s: %s", msg, err.Error())
return ruleResponse(*rule, ruleType, msg, engineapi.RuleStatusError)
}
func ruleResponse(rule kyvernov1.Rule, ruleType engineapi.RuleType, msg string, status engineapi.RuleStatus) *engineapi.RuleResponse {
resp := &engineapi.RuleResponse{
Name: rule.Name,
Type: ruleType,
Message: msg,
Status: status,
}
return resp
}
func incrementAppliedCount(resp *engineapi.EngineResponse) {
resp.PolicyResponse.RulesAppliedCount++
}

View file

@ -14,10 +14,10 @@ import (
"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/internal"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/validate"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/pss"
"github.com/kyverno/kyverno/pkg/tracing"
"github.com/kyverno/kyverno/pkg/utils/api"
@ -34,66 +34,13 @@ import (
func (e *engine) validate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) (resp *engineapi.EngineResponse) {
resp = &engineapi.EngineResponse{}
) *engineapi.EngineResponse {
startTime := time.Now()
logger := buildLogger(policyContext)
logger := internal.BuildLogger(policyContext)
logger.V(4).Info("start validate policy processing", "startTime", startTime)
defer func() {
buildResponse(policyContext, resp, startTime)
logger.V(4).Info("finished policy processing", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "validationRulesApplied", resp.PolicyResponse.RulesAppliedCount)
}()
resp = e.validateResource(ctx, logger, policyContext)
resp.NamespaceLabels = policyContext.NamespaceLabels()
return
}
func buildLogger(ctx engineapi.PolicyContext) logr.Logger {
logger := logging.WithName("EngineValidate").WithValues("policy", ctx.Policy().GetName())
newResource := ctx.NewResource()
oldResource := ctx.OldResource()
if reflect.DeepEqual(newResource, unstructured.Unstructured{}) {
logger = logger.WithValues("kind", oldResource.GetKind(), "namespace", oldResource.GetNamespace(), "name", oldResource.GetName())
} else {
logger = logger.WithValues("kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName())
}
return logger
}
func buildResponse(ctx engineapi.PolicyContext, resp *engineapi.EngineResponse, startTime time.Time) {
if reflect.DeepEqual(resp, engineapi.EngineResponse{}) {
return
}
if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) {
// for delete requests patched resource will be oldResource since newResource is empty
resource := ctx.NewResource()
if reflect.DeepEqual(resource, unstructured.Unstructured{}) {
resource = ctx.OldResource()
}
resp.PatchedResource = resource
}
policy := ctx.Policy()
resp.Policy = policy
resp.PolicyResponse.Policy.Name = policy.GetName()
resp.PolicyResponse.Policy.Namespace = policy.GetNamespace()
resp.PolicyResponse.Resource.Name = resp.PatchedResource.GetName()
resp.PolicyResponse.Resource.Namespace = resp.PatchedResource.GetNamespace()
resp.PolicyResponse.Resource.Kind = resp.PatchedResource.GetKind()
resp.PolicyResponse.Resource.APIVersion = resp.PatchedResource.GetAPIVersion()
resp.PolicyResponse.ValidationFailureAction = policy.GetSpec().ValidationFailureAction
for _, v := range policy.GetSpec().ValidationFailureActionOverrides {
newOverrides := engineapi.ValidationFailureActionOverride{Action: v.Action, Namespaces: v.Namespaces, NamespaceSelector: v.NamespaceSelector}
resp.PolicyResponse.ValidationFailureActionOverrides = append(resp.PolicyResponse.ValidationFailureActionOverrides, newOverrides)
}
resp.PolicyResponse.ProcessingTime = time.Since(startTime)
resp.PolicyResponse.Timestamp = startTime.Unix()
resp := e.validateResource(ctx, logger, policyContext)
defer logger.V(4).Info("finished policy processing", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "validationRulesApplied", resp.PolicyResponse.RulesAppliedCount)
return internal.BuildResponse(policyContext, resp, startTime)
}
func (e *engine) validateResource(
@ -265,16 +212,16 @@ func newForEachValidator(
func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if err := v.loadContext(ctx); err != nil {
return ruleError(v.rule, engineapi.Validation, "failed to load context", err)
return internal.RuleError(v.rule, engineapi.Validation, "failed to load context", err)
}
preconditionsPassed, err := checkPreconditions(v.log, v.policyContext, v.anyAllConditions)
if err != nil {
return ruleError(v.rule, engineapi.Validation, "failed to evaluate preconditions", err)
return internal.RuleError(v.rule, engineapi.Validation, "failed to evaluate preconditions", err)
}
if !preconditionsPassed {
return ruleResponse(*v.rule, engineapi.Validation, "preconditions not met", engineapi.RuleStatusSkip)
return internal.RuleResponse(*v.rule, engineapi.Validation, "preconditions not met", engineapi.RuleStatusSkip)
}
if v.deny != nil {
@ -283,7 +230,7 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if v.pattern != nil || v.anyPattern != nil {
if err = v.substitutePatterns(); err != nil {
return ruleError(v.rule, engineapi.Validation, "variable substitution failed", err)
return internal.RuleError(v.rule, engineapi.Validation, "variable substitution failed", err)
}
ruleResponse := v.validateResourceWithRule()
@ -309,12 +256,11 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
func (v *validator) validateForEach(ctx context.Context) *engineapi.RuleResponse {
applyCount := 0
for _, foreach := range v.forEach {
elements, err := evaluateList(foreach.List, (v.policyContext.JSONContext()))
elements, err := evaluateList(foreach.List, v.policyContext.JSONContext())
if err != nil {
v.log.V(2).Info("failed to evaluate list", "list", foreach.List, "error", err.Error())
continue
}
resp, count := v.validateElements(ctx, foreach, elements, foreach.ElementScope)
if resp.Status != engineapi.RuleStatusPass {
return resp
@ -325,9 +271,9 @@ func (v *validator) validateForEach(ctx context.Context) *engineapi.RuleResponse
if v.forEach == nil {
return nil
}
return ruleResponse(*v.rule, engineapi.Validation, "rule skipped", engineapi.RuleStatusSkip)
return internal.RuleResponse(*v.rule, engineapi.Validation, "rule skipped", engineapi.RuleStatusSkip)
}
return ruleResponse(*v.rule, engineapi.Validation, "rule passed", engineapi.RuleStatusPass)
return internal.RuleResponse(*v.rule, engineapi.Validation, "rule passed", engineapi.RuleStatusPass)
}
func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*engineapi.RuleResponse, int) {
@ -344,13 +290,13 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
policyContext := v.policyContext.Copy()
if err := addElementToContext(policyContext, element, index, v.nesting, elementScope); err != nil {
v.log.Error(err, "failed to add element to context")
return ruleError(v.rule, engineapi.Validation, "failed to process foreach", err), applyCount
return internal.RuleError(v.rule, engineapi.Validation, "failed to process foreach", err), applyCount
}
foreachValidator, err := newForEachValidator(foreach, v.contextLoader, v.nesting+1, v.rule, policyContext, v.log)
if err != nil {
v.log.Error(err, "failed to create foreach validator")
return ruleError(v.rule, engineapi.Validation, "failed to create foreach validator", err), applyCount
return internal.RuleError(v.rule, engineapi.Validation, "failed to create foreach validator", err), applyCount
}
r := foreachValidator.validate(ctx)
@ -366,16 +312,16 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
continue
}
msg := fmt.Sprintf("validation failure: %v", r.Message)
return ruleResponse(*v.rule, engineapi.Validation, msg, r.Status), applyCount
return internal.RuleResponse(*v.rule, engineapi.Validation, msg, r.Status), applyCount
}
msg := fmt.Sprintf("validation failure: %v", r.Message)
return ruleResponse(*v.rule, engineapi.Validation, msg, r.Status), applyCount
return internal.RuleResponse(*v.rule, engineapi.Validation, msg, r.Status), applyCount
}
applyCount++
}
return ruleResponse(*v.rule, engineapi.Validation, "", engineapi.RuleStatusPass), applyCount
return internal.RuleResponse(*v.rule, engineapi.Validation, "", engineapi.RuleStatusPass), applyCount
}
func addElementToContext(ctx engineapi.PolicyContext, element interface{}, index, nesting int, elementScope *bool) error {
@ -411,7 +357,7 @@ func addElementToContext(ctx engineapi.PolicyContext, element interface{}, index
}
func (v *validator) loadContext(ctx context.Context) error {
if err := LoadContext(ctx, v.contextLoader, v.contextEntries, v.policyContext, v.rule.Name); err != nil {
if err := internal.LoadContext(ctx, v.contextLoader, v.contextEntries, v.policyContext, v.rule.Name); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
v.log.V(3).Info("failed to load context", "reason", err.Error())
} else {
@ -428,24 +374,24 @@ func (v *validator) validateDeny() *engineapi.RuleResponse {
anyAllCond := v.deny.GetAnyAllConditions()
anyAllCond, err := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), anyAllCond)
if err != nil {
return ruleError(v.rule, engineapi.Validation, "failed to substitute variables in deny conditions", err)
return internal.RuleError(v.rule, engineapi.Validation, "failed to substitute variables in deny conditions", err)
}
if err = v.substituteDeny(); err != nil {
return ruleError(v.rule, engineapi.Validation, "failed to substitute variables in rule", err)
return internal.RuleError(v.rule, engineapi.Validation, "failed to substitute variables in rule", err)
}
denyConditions, err := utils.TransformConditions(anyAllCond)
if err != nil {
return ruleError(v.rule, engineapi.Validation, "invalid deny conditions", err)
return internal.RuleError(v.rule, engineapi.Validation, "invalid deny conditions", err)
}
deny := variables.EvaluateConditions(v.log, v.policyContext.JSONContext(), denyConditions)
if deny {
return ruleResponse(*v.rule, engineapi.Validation, v.getDenyMessage(deny), engineapi.RuleStatusFail)
return internal.RuleResponse(*v.rule, engineapi.Validation, v.getDenyMessage(deny), engineapi.RuleStatusFail)
}
return ruleResponse(*v.rule, engineapi.Validation, v.getDenyMessage(deny), engineapi.RuleStatusPass)
return internal.RuleResponse(*v.rule, engineapi.Validation, v.getDenyMessage(deny), engineapi.RuleStatusPass)
}
func (v *validator) getDenyMessage(deny bool) string {
@ -526,7 +472,7 @@ func (v *validator) validatePodSecurity() *engineapi.RuleResponse {
// Marshal pod metadata and spec
podSpec, metadata, err := getSpec(v)
if err != nil {
return ruleError(v.rule, engineapi.Validation, "Error while getting new resource", err)
return internal.RuleError(v.rule, engineapi.Validation, "Error while getting new resource", err)
}
pod := &corev1.Pod{
@ -535,7 +481,7 @@ func (v *validator) validatePodSecurity() *engineapi.RuleResponse {
}
allowed, pssChecks, err := pss.EvaluatePod(v.podSecurity, pod)
if err != nil {
return ruleError(v.rule, engineapi.Validation, "failed to parse pod security api version", err)
return internal.RuleError(v.rule, engineapi.Validation, "failed to parse pod security api version", err)
}
podSecurityChecks := &engineapi.PodSecurityChecks{
Level: v.podSecurity.Level,
@ -544,12 +490,12 @@ func (v *validator) validatePodSecurity() *engineapi.RuleResponse {
}
if allowed {
msg := fmt.Sprintf("Validation rule '%s' passed.", v.rule.Name)
rspn := ruleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusPass)
rspn := internal.RuleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusPass)
rspn.PodSecurityChecks = podSecurityChecks
return rspn
} else {
msg := fmt.Sprintf(`Validation rule '%s' failed. It violates PodSecurity "%s:%s": %s`, v.rule.Name, v.podSecurity.Level, v.podSecurity.Version, pss.FormatChecksPrint(pssChecks))
rspn := ruleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusFail)
rspn := internal.RuleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusFail)
rspn.PodSecurityChecks = podSecurityChecks
return rspn
}
@ -619,22 +565,22 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
v.log.V(3).Info("validation error", "path", pe.Path, "error", err.Error())
if pe.Skip {
return ruleResponse(*v.rule, engineapi.Validation, pe.Error(), engineapi.RuleStatusSkip)
return internal.RuleResponse(*v.rule, engineapi.Validation, pe.Error(), engineapi.RuleStatusSkip)
}
if pe.Path == "" {
return ruleResponse(*v.rule, engineapi.Validation, v.buildErrorMessage(err, ""), engineapi.RuleStatusError)
return internal.RuleResponse(*v.rule, engineapi.Validation, v.buildErrorMessage(err, ""), engineapi.RuleStatusError)
}
return ruleResponse(*v.rule, engineapi.Validation, v.buildErrorMessage(err, pe.Path), engineapi.RuleStatusFail)
return internal.RuleResponse(*v.rule, engineapi.Validation, v.buildErrorMessage(err, pe.Path), engineapi.RuleStatusFail)
}
return ruleResponse(*v.rule, engineapi.Validation, v.buildErrorMessage(err, pe.Path), engineapi.RuleStatusError)
return internal.RuleResponse(*v.rule, engineapi.Validation, v.buildErrorMessage(err, pe.Path), engineapi.RuleStatusError)
}
v.log.V(4).Info("successfully processed rule")
msg := fmt.Sprintf("validation rule '%s' passed.", v.rule.Name)
return ruleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusPass)
return internal.RuleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusPass)
}
if v.anyPattern != nil {
@ -645,14 +591,14 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
anyPatterns, err := deserializeAnyPattern(v.anyPattern)
if err != nil {
msg := fmt.Sprintf("failed to deserialize anyPattern, expected type array: %v", err)
return ruleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusError)
return internal.RuleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusError)
}
for idx, pattern := range anyPatterns {
err := validate.MatchPattern(v.log, resource.Object, pattern)
if err == nil {
msg := fmt.Sprintf("validation rule '%s' anyPattern[%d] passed.", v.rule.Name, idx)
return ruleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusPass)
return internal.RuleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusPass)
}
if pe, ok := err.(*validate.PatternError); ok {
@ -681,7 +627,7 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
}
v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' skipped. %s", v.rule.Name, errorStr))
return ruleResponse(*v.rule, engineapi.Validation, strings.Join(errorStr, " "), engineapi.RuleStatusSkip)
return internal.RuleResponse(*v.rule, engineapi.Validation, strings.Join(errorStr, " "), engineapi.RuleStatusSkip)
} else if len(failedAnyPatternsErrors) > 0 {
var errorStr []string
for _, err := range failedAnyPatternsErrors {
@ -690,11 +636,11 @@ func (v *validator) validatePatterns(resource unstructured.Unstructured) *engine
v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' failed. %s", v.rule.Name, errorStr))
msg := buildAnyPatternErrorMessage(v.rule, errorStr)
return ruleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusFail)
return internal.RuleResponse(*v.rule, engineapi.Validation, msg, engineapi.RuleStatusFail)
}
}
return ruleResponse(*v.rule, engineapi.Validation, v.rule.Validation.Message, engineapi.RuleStatusPass)
return internal.RuleResponse(*v.rule, engineapi.Validation, v.rule.Validation.Message, engineapi.RuleStatusPass)
}
func deserializeAnyPattern(anyPattern apiextensions.JSON) ([]interface{}, error) {