1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

refactor: introduce policy context interface in engine api (#6177)

* refactor: introduce policy context interface in engine api

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>

* more interface funcs

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

* interface

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

* rename

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

* merge main

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

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-01-31 16:28:48 +01:00 committed by GitHub
parent 848596ca8d
commit 8290112b84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 326 additions and 245 deletions

View file

@ -461,10 +461,7 @@ OuterLoop:
log.Log.Error(err, "failed to add image variables to context") log.Log.Error(err, "failed to add image variables to context")
} }
subresources := make([]struct { subresources := make([]engineapi.SubResource, 0)
APIResource metav1.APIResource
ParentResource metav1.APIResource
}, 0)
// If --cluster flag is not set, then we need to add subresources to the context // If --cluster flag is not set, then we need to add subresources to the context
if c.Client == nil { if c.Client == nil {

View file

@ -0,0 +1,45 @@
package api
import (
"context"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// ExcludeFunc is a function used to determine if a resource is excluded
type ExcludeFunc = func(kind, namespace, name string) bool
type SubResource struct {
APIResource metav1.APIResource
ParentResource metav1.APIResource
}
type PolicyContext interface {
Policy() kyvernov1.PolicyInterface
NewResource() unstructured.Unstructured
OldResource() unstructured.Unstructured
AdmissionInfo() kyvernov1beta1.RequestInfo
NamespaceLabels() map[string]string
SubResource() string
SubresourcesInPolicy() []SubResource
ExcludeGroupRole() []string
AdmissionOperation() bool
RequestResource() metav1.GroupVersionResource
Element() unstructured.Unstructured
SetElement(element unstructured.Unstructured)
JSONContext() enginecontext.Interface
Client() dclient.Interface
Copy() PolicyContext
FindExceptions(rule string) ([]*kyvernov2alpha1.PolicyException, error)
ExcludeResourceFunc() ExcludeFunc
ResolveConfigMap(ctx context.Context, namespace string, name string) (*corev1.ConfigMap, error)
}

View file

@ -19,7 +19,7 @@ import (
// 2. returns the list of rules that are applicable on this policy and resource, if 1 succeed // 2. returns the list of rules that are applicable on this policy and resource, if 1 succeed
func ApplyBackgroundChecks( func ApplyBackgroundChecks(
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
) (resp *engineapi.EngineResponse) { ) (resp *engineapi.EngineResponse) {
policyStartTime := time.Now() policyStartTime := time.Now()
return filterRules(contextLoader, policyContext, policyStartTime) return filterRules(contextLoader, policyContext, policyStartTime)
@ -27,18 +27,20 @@ func ApplyBackgroundChecks(
func filterRules( func filterRules(
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
startTime time.Time, startTime time.Time,
) *engineapi.EngineResponse { ) *engineapi.EngineResponse {
kind := policyContext.newResource.GetKind() newResource := policyContext.NewResource()
name := policyContext.newResource.GetName() policy := policyContext.Policy()
namespace := policyContext.newResource.GetNamespace() kind := newResource.GetKind()
apiVersion := policyContext.newResource.GetAPIVersion() name := newResource.GetName()
namespace := newResource.GetNamespace()
apiVersion := newResource.GetAPIVersion()
resp := &engineapi.EngineResponse{ resp := &engineapi.EngineResponse{
PolicyResponse: engineapi.PolicyResponse{ PolicyResponse: engineapi.PolicyResponse{
Policy: engineapi.PolicySpec{ Policy: engineapi.PolicySpec{
Name: policyContext.policy.GetName(), Name: policy.GetName(),
Namespace: policyContext.policy.GetNamespace(), Namespace: policy.GetNamespace(),
}, },
PolicyStats: engineapi.PolicyStats{ PolicyStats: engineapi.PolicyStats{
ExecutionStats: engineapi.ExecutionStats{ ExecutionStats: engineapi.ExecutionStats{
@ -54,13 +56,13 @@ func filterRules(
}, },
} }
if policyContext.excludeResourceFunc(kind, namespace, name) { if policyContext.ExcludeResourceFunc()(kind, namespace, name) {
logging.WithName("ApplyBackgroundChecks").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name) logging.WithName("ApplyBackgroundChecks").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name)
return resp return resp
} }
applyRules := policyContext.policy.GetSpec().GetApplyRules() applyRules := policy.GetSpec().GetApplyRules()
for _, rule := range autogen.ComputeRules(policyContext.policy) { for _, rule := range autogen.ComputeRules(policy) {
if ruleResp := filterRule(contextLoader, rule, policyContext); ruleResp != nil { if ruleResp := filterRule(contextLoader, rule, policyContext); ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
if applyRules == kyvernov1.ApplyOne && ruleResp.Status != engineapi.RuleStatusSkip { if applyRules == kyvernov1.ApplyOne && ruleResp.Status != engineapi.RuleStatusSkip {
@ -75,7 +77,7 @@ func filterRules(
func filterRule( func filterRule(
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
rule kyvernov1.Rule, rule kyvernov1.Rule,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
) *engineapi.RuleResponse { ) *engineapi.RuleResponse {
if !rule.HasGenerate() && !rule.IsMutateExisting() { if !rule.HasGenerate() && !rule.IsMutateExisting() {
return nil return nil
@ -99,21 +101,21 @@ func filterRule(
startTime := time.Now() startTime := time.Now()
policy := policyContext.policy policy := policyContext.Policy()
newResource := policyContext.newResource newResource := policyContext.NewResource()
oldResource := policyContext.oldResource oldResource := policyContext.OldResource()
admissionInfo := policyContext.admissionInfo admissionInfo := policyContext.AdmissionInfo()
ctx := policyContext.jsonContext ctx := policyContext.JSONContext()
excludeGroupRole := policyContext.excludeGroupRole excludeGroupRole := policyContext.ExcludeGroupRole()
namespaceLabels := policyContext.namespaceLabels namespaceLabels := policyContext.NamespaceLabels()
logger = logging.WithName(string(ruleType)).WithValues("policy", policy.GetName(), logger = logging.WithName(string(ruleType)).WithValues("policy", policy.GetName(),
"kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName()) "kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName())
if err := MatchesResourceDescription(subresourceGVKToAPIResource, newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, "", policyContext.subresource); err != nil { if err := MatchesResourceDescription(subresourceGVKToAPIResource, newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, "", policyContext.SubResource()); err != nil {
if ruleType == engineapi.Generation { if ruleType == engineapi.Generation {
// if the oldResource matched, return "false" to delete GR for it // if the oldResource matched, return "false" to delete GR for it
if err = MatchesResourceDescription(subresourceGVKToAPIResource, oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, "", policyContext.subresource); err == nil { if err = MatchesResourceDescription(subresourceGVKToAPIResource, oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, "", policyContext.SubResource()); err == nil {
return &engineapi.RuleResponse{ return &engineapi.RuleResponse{
Name: rule.Name, Name: rule.Name,
Type: ruleType, Type: ruleType,
@ -129,8 +131,8 @@ func filterRule(
return nil return nil
} }
policyContext.jsonContext.Checkpoint() policyContext.JSONContext().Checkpoint()
defer policyContext.jsonContext.Restore() defer policyContext.JSONContext().Restore()
if err := LoadContext(context.TODO(), contextLoader, rule.Context, policyContext, rule.Name); err != nil { if err := LoadContext(context.TODO(), contextLoader, rule.Context, policyContext, rule.Name); err != nil {
logger.V(4).Info("cannot add external data to the context", "reason", err.Error()) logger.V(4).Info("cannot add external data to the context", "reason", err.Error())

View file

@ -3,20 +3,22 @@ package engine
import ( import (
"strings" "strings"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// GetSubresourceGVKToAPIResourceMap returns a map of subresource GVK to APIResource. This is used to determine if a resource is a subresource. // GetSubresourceGVKToAPIResourceMap returns a map of subresource GVK to APIResource. This is used to determine if a resource is a subresource.
func GetSubresourceGVKToAPIResourceMap(kindsInPolicy []string, ctx *PolicyContext) map[string]*metav1.APIResource { func GetSubresourceGVKToAPIResourceMap(kindsInPolicy []string, ctx engineapi.PolicyContext) map[string]*metav1.APIResource {
subresourceGVKToAPIResource := make(map[string]*metav1.APIResource) subresourceGVKToAPIResource := make(map[string]*metav1.APIResource)
for _, gvk := range kindsInPolicy { for _, gvk := range kindsInPolicy {
gv, k := kubeutils.GetKindFromGVK(gvk) gv, k := kubeutils.GetKindFromGVK(gvk)
parentKind, subresource := kubeutils.SplitSubresource(k) parentKind, subresource := kubeutils.SplitSubresource(k)
// Len of subresources is non zero only when validation request was sent from CLI without connecting to the cluster. // Len of subresources is non zero only when validation request was sent from CLI without connecting to the cluster.
if len(ctx.subresourcesInPolicy) != 0 { subresourcesInPolicy := ctx.SubresourcesInPolicy()
if len(subresourcesInPolicy) != 0 {
if subresource != "" { if subresource != "" {
for _, subresourceInPolicy := range ctx.subresourcesInPolicy { for _, subresourceInPolicy := range subresourcesInPolicy {
parentResourceGroupVersion := metav1.GroupVersion{ parentResourceGroupVersion := metav1.GroupVersion{
Group: subresourceInPolicy.ParentResource.Group, Group: subresourceInPolicy.ParentResource.Group,
Version: subresourceInPolicy.ParentResource.Version, Version: subresourceInPolicy.ParentResource.Version,
@ -31,7 +33,7 @@ func GetSubresourceGVKToAPIResourceMap(kindsInPolicy []string, ctx *PolicyContex
} }
} }
} else { // Complete kind may be a subresource, for eg- 'PodExecOptions' } else { // Complete kind may be a subresource, for eg- 'PodExecOptions'
for _, subresourceInPolicy := range ctx.subresourcesInPolicy { for _, subresourceInPolicy := range subresourcesInPolicy {
// Subresources which can be just specified by kind, for eg- 'PodExecOptions' // Subresources which can be just specified by kind, for eg- 'PodExecOptions'
// have different kind than their parent resource. Otherwise for subresources which // have different kind than their parent resource. Otherwise for subresources which
// have same kind as parent resource, need to be specified as Kind/Subresource, eg - 'Pod/status' // have same kind as parent resource, need to be specified as Kind/Subresource, eg - 'Pod/status'
@ -48,9 +50,9 @@ func GetSubresourceGVKToAPIResourceMap(kindsInPolicy []string, ctx *PolicyContex
} }
} }
} }
} else if ctx.client != nil { } else if ctx.Client() != nil {
// find the resource from API client // find the resource from API client
apiResource, _, _, err := ctx.client.Discovery().FindResource(gv, k) apiResource, _, _, err := ctx.Client().Discovery().FindResource(gv, k)
if err == nil { if err == nil {
if kubeutils.IsSubresource(apiResource.Name) { if kubeutils.IsSubresource(apiResource.Name) {
subresourceGVKToAPIResource[gvk] = apiResource subresourceGVKToAPIResource[gvk] = apiResource

View file

@ -3,6 +3,7 @@ package engine
import ( import (
"testing" "testing"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"gotest.tools/assert" "gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -37,10 +38,7 @@ func Test_GetSubresourceGVKToAPIResourceMap(t *testing.T) {
} }
policyContext := NewPolicyContext(). policyContext := NewPolicyContext().
WithSubresourcesInPolicy([]struct { WithSubresourcesInPolicy([]engineapi.SubResource{
APIResource metav1.APIResource
ParentResource metav1.APIResource
}{
{ {
APIResource: podStatusAPIResource, APIResource: podStatusAPIResource,
ParentResource: podAPIResource, ParentResource: podAPIResource,

View file

@ -13,7 +13,7 @@ import (
// GenerateResponse checks for validity of generate rule on the resource // GenerateResponse checks for validity of generate rule on the resource
func GenerateResponse( func GenerateResponse(
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
gr kyvernov1beta1.UpdateRequest, gr kyvernov1beta1.UpdateRequest,
) (resp *engineapi.EngineResponse) { ) (resp *engineapi.EngineResponse) {
policyStartTime := time.Now() policyStartTime := time.Now()
@ -22,19 +22,19 @@ func GenerateResponse(
func filterGenerateRules( func filterGenerateRules(
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
policyNameKey string, policyNameKey string,
startTime time.Time, startTime time.Time,
) *engineapi.EngineResponse { ) *engineapi.EngineResponse {
kind := policyContext.newResource.GetKind() newResource := policyContext.NewResource()
name := policyContext.newResource.GetName() kind := newResource.GetKind()
namespace := policyContext.newResource.GetNamespace() name := newResource.GetName()
apiVersion := policyContext.newResource.GetAPIVersion() namespace := newResource.GetNamespace()
apiVersion := newResource.GetAPIVersion()
pNamespace, pName, err := cache.SplitMetaNamespaceKey(policyNameKey) pNamespace, pName, err := cache.SplitMetaNamespaceKey(policyNameKey)
if err != nil { if err != nil {
logging.Error(err, "failed to spilt name and namespace", policyNameKey) logging.Error(err, "failed to spilt name and namespace", policyNameKey)
} }
resp := &engineapi.EngineResponse{ resp := &engineapi.EngineResponse{
PolicyResponse: engineapi.PolicyResponse{ PolicyResponse: engineapi.PolicyResponse{
Policy: engineapi.PolicySpec{ Policy: engineapi.PolicySpec{
@ -54,13 +54,12 @@ func filterGenerateRules(
}, },
}, },
} }
if policyContext.ExcludeResourceFunc()(kind, namespace, name) {
if policyContext.excludeResourceFunc(kind, namespace, name) {
logging.WithName("Generate").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name) logging.WithName("Generate").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name)
return resp return resp
} }
for _, rule := range autogen.ComputeRules(policyContext.policy) { for _, rule := range autogen.ComputeRules(policyContext.Policy()) {
if ruleResp := filterRule(contextLoader, rule, policyContext); ruleResp != nil { if ruleResp := filterRule(contextLoader, rule, policyContext); ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
} }

View file

@ -47,15 +47,15 @@ func getMatchingImages(images map[string]map[string]apiutils.ImageInfo, rule *ky
return imageInfos, strings.Join(imageRefs, ",") return imageInfos, strings.Join(imageRefs, ",")
} }
func extractMatchingImages(policyContext *PolicyContext, rule *kyvernov1.Rule, cfg config.Configuration) ([]apiutils.ImageInfo, string, error) { func extractMatchingImages(policyContext engineapi.PolicyContext, rule *kyvernov1.Rule, cfg config.Configuration) ([]apiutils.ImageInfo, string, error) {
var ( var (
images map[string]map[string]apiutils.ImageInfo images map[string]map[string]apiutils.ImageInfo
err error err error
) )
images = policyContext.jsonContext.ImageInfo() newResource := policyContext.NewResource()
images = policyContext.JSONContext().ImageInfo()
if rule.ImageExtractors != nil { if rule.ImageExtractors != nil {
images, err = policyContext.jsonContext.GenerateCustomImageInfo( images, err = policyContext.JSONContext().GenerateCustomImageInfo(&newResource, rule.ImageExtractors, cfg)
&policyContext.newResource, rule.ImageExtractors, cfg)
if err != nil { if err != nil {
// if we get an error while generating custom images from image extractors, // if we get an error while generating custom images from image extractors,
// don't check for matching images in imageExtractors // don't check for matching images in imageExtractors
@ -70,13 +70,13 @@ func VerifyAndPatchImages(
ctx context.Context, ctx context.Context,
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
rclient registryclient.Client, rclient registryclient.Client,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
cfg config.Configuration, cfg config.Configuration,
) (*engineapi.EngineResponse, *ImageVerificationMetadata) { ) (*engineapi.EngineResponse, *ImageVerificationMetadata) {
resp := &engineapi.EngineResponse{} resp := &engineapi.EngineResponse{}
policy := policyContext.policy policy := policyContext.Policy()
patchedResource := policyContext.newResource patchedResource := policyContext.NewResource()
logger := logging.WithName("EngineVerifyImages").WithValues("policy", policy.GetName(), logger := logging.WithName("EngineVerifyImages").WithValues("policy", policy.GetName(),
"kind", patchedResource.GetKind(), "namespace", patchedResource.GetNamespace(), "name", patchedResource.GetName()) "kind", patchedResource.GetKind(), "namespace", patchedResource.GetNamespace(), "name", patchedResource.GetName())
@ -88,11 +88,11 @@ func VerifyAndPatchImages(
"applied", resp.PolicyResponse.RulesAppliedCount, "successful", resp.IsSuccessful()) "applied", resp.PolicyResponse.RulesAppliedCount, "successful", resp.IsSuccessful())
}() }()
policyContext.jsonContext.Checkpoint() policyContext.JSONContext().Checkpoint()
defer policyContext.jsonContext.Restore() defer policyContext.JSONContext().Restore()
ivm := &ImageVerificationMetadata{} ivm := &ImageVerificationMetadata{}
rules := autogen.ComputeRules(policyContext.policy) rules := autogen.ComputeRules(policyContext.Policy())
applyRules := policy.GetSpec().GetApplyRules() applyRules := policy.GetSpec().GetApplyRules()
for i := range rules { for i := range rules {
@ -138,13 +138,13 @@ func VerifyAndPatchImages(
return return
} }
policyContext.jsonContext.Restore() policyContext.JSONContext().Restore()
if err := LoadContext(ctx, contextLoader, rule.Context, policyContext, rule.Name); err != nil { if err := LoadContext(ctx, contextLoader, rule.Context, policyContext, rule.Name); err != nil {
appendResponse(resp, rule, fmt.Sprintf("failed to load context: %s", err.Error()), engineapi.RuleStatusError) appendResponse(resp, rule, fmt.Sprintf("failed to load context: %s", err.Error()), engineapi.RuleStatusError)
return return
} }
ruleCopy, err := substituteVariables(rule, policyContext.jsonContext, logger) ruleCopy, err := substituteVariables(rule, policyContext.JSONContext(), logger)
if err != nil { if err != nil {
appendResponse(resp, rule, fmt.Sprintf("failed to substitute variables: %s", err.Error()), engineapi.RuleStatusError) appendResponse(resp, rule, fmt.Sprintf("failed to substitute variables: %s", err.Error()), engineapi.RuleStatusError)
return return
@ -203,7 +203,7 @@ func substituteVariables(rule *kyvernov1.Rule, ctx enginecontext.EvalInterface,
type imageVerifier struct { type imageVerifier struct {
logger logr.Logger logger logr.Logger
rclient registryclient.Client rclient registryclient.Client
policyContext *PolicyContext policyContext engineapi.PolicyContext
rule *kyvernov1.Rule rule *kyvernov1.Rule
resp *engineapi.EngineResponse resp *engineapi.EngineResponse
ivm *ImageVerificationMetadata ivm *ImageVerificationMetadata
@ -228,13 +228,13 @@ func (iv *imageVerifier) verify(ctx context.Context, imageVerify kyvernov1.Image
} }
pointer := jsonpointer.ParsePath(imageInfo.Pointer).JMESPath() pointer := jsonpointer.ParsePath(imageInfo.Pointer).JMESPath()
changed, err := iv.policyContext.jsonContext.HasChanged(pointer) changed, err := iv.policyContext.JSONContext().HasChanged(pointer)
if err == nil && !changed { if err == nil && !changed {
iv.logger.V(4).Info("no change in image, skipping check", "image", image) iv.logger.V(4).Info("no change in image, skipping check", "image", image)
continue continue
} }
verified, err := isImageVerified(iv.policyContext.newResource, image, iv.logger) verified, err := isImageVerified(iv.policyContext.NewResource(), image, iv.logger)
if err == nil && verified { if err == nil && verified {
iv.logger.Info("image was previously verified, skipping check", "image", image) iv.logger.Info("image was previously verified, skipping check", "image", image)
continue continue
@ -292,15 +292,17 @@ func (iv *imageVerifier) handleMutateDigest(ctx context.Context, digest string,
return patch, digest, nil return patch, digest, nil
} }
func hasImageVerifiedAnnotationChanged(ctx *PolicyContext, log logr.Logger) bool { func hasImageVerifiedAnnotationChanged(ctx engineapi.PolicyContext, log logr.Logger) bool {
if reflect.DeepEqual(ctx.newResource, unstructured.Unstructured{}) || newResource := ctx.NewResource()
reflect.DeepEqual(ctx.oldResource, unstructured.Unstructured{}) { oldResource := ctx.OldResource()
if reflect.DeepEqual(newResource, unstructured.Unstructured{}) ||
reflect.DeepEqual(oldResource, unstructured.Unstructured{}) {
return false return false
} }
key := imageVerifyAnnotationKey key := imageVerifyAnnotationKey
newValue := ctx.newResource.GetAnnotations()[key] newValue := newResource.GetAnnotations()[key]
oldValue := ctx.oldResource.GetAnnotations()[key] oldValue := oldResource.GetAnnotations()[key]
result := newValue != oldValue result := newValue != oldValue
if result { if result {
log.V(2).Info("annotation mismatch", "oldValue", oldValue, "newValue", newValue, "key", key) log.V(2).Info("annotation mismatch", "oldValue", oldValue, "newValue", newValue, "key", key)
@ -333,7 +335,7 @@ func (iv *imageVerifier) verifyImage(
iv.logger.V(2).Info("verifying image signatures", "image", image, iv.logger.V(2).Info("verifying image signatures", "image", image,
"attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations)) "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations))
if err := iv.policyContext.jsonContext.AddImageInfo(imageInfo, cfg); err != nil { if err := iv.policyContext.JSONContext().AddImageInfo(imageInfo, cfg); err != nil {
iv.logger.Error(err, "failed to add image to context") iv.logger.Error(err, "failed to add image to context")
msg := fmt.Sprintf("failed to add image to context %s: %s", image, err.Error()) msg := fmt.Sprintf("failed to add image to context %s: %s", image, err.Error())
return ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusError), "" return ruleResponse(*iv.rule, engineapi.ImageVerify, msg, engineapi.RuleStatusError), ""
@ -698,10 +700,10 @@ func (iv *imageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string
return true, nil return true, nil
} }
iv.policyContext.jsonContext.Checkpoint() iv.policyContext.JSONContext().Checkpoint()
defer iv.policyContext.jsonContext.Restore() defer iv.policyContext.JSONContext().Restore()
return evaluateConditions(a.Conditions, iv.policyContext.jsonContext, s, iv.logger) return evaluateConditions(a.Conditions, iv.policyContext.JSONContext(), s, iv.logger)
} }
func evaluateConditions( func evaluateConditions(

View file

@ -19,7 +19,7 @@ func processImageValidationRule(
ctx context.Context, ctx context.Context,
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
log logr.Logger, log logr.Logger,
enginectx *PolicyContext, enginectx engineapi.PolicyContext,
rule *kyvernov1.Rule, rule *kyvernov1.Rule,
cfg config.Configuration, cfg config.Configuration,
) *engineapi.RuleResponse { ) *engineapi.RuleResponse {
@ -51,7 +51,7 @@ func processImageValidationRule(
} }
if !preconditionsPassed { if !preconditionsPassed {
if enginectx.policy.GetSpec().ValidationFailureAction.Audit() { if enginectx.Policy().GetSpec().ValidationFailureAction.Audit() {
return nil return nil
} }
@ -60,7 +60,7 @@ func processImageValidationRule(
for _, v := range rule.VerifyImages { for _, v := range rule.VerifyImages {
imageVerify := v.Convert() imageVerify := v.Convert()
for _, infoMap := range enginectx.jsonContext.ImageInfo() { for _, infoMap := range enginectx.JSONContext().ImageInfo() {
for name, imageInfo := range infoMap { for name, imageInfo := range infoMap {
image := imageInfo.String() image := imageInfo.String()
log = log.WithValues("rule", rule.Name) log = log.WithValues("rule", rule.Name)
@ -82,24 +82,22 @@ func processImageValidationRule(
return ruleResponse(*rule, engineapi.Validation, "image verified", engineapi.RuleStatusPass) return ruleResponse(*rule, engineapi.Validation, "image verified", engineapi.RuleStatusPass)
} }
func validateImage(ctx *PolicyContext, imageVerify *kyvernov1.ImageVerification, name string, imageInfo apiutils.ImageInfo, log logr.Logger) error { func validateImage(ctx engineapi.PolicyContext, imageVerify *kyvernov1.ImageVerification, name string, imageInfo apiutils.ImageInfo, log logr.Logger) error {
image := imageInfo.String() image := imageInfo.String()
if imageVerify.VerifyDigest && imageInfo.Digest == "" { if imageVerify.VerifyDigest && imageInfo.Digest == "" {
log.V(2).Info("missing digest", "image", imageInfo.String()) log.V(2).Info("missing digest", "image", imageInfo.String())
return fmt.Errorf("missing digest for %s", image) return fmt.Errorf("missing digest for %s", image)
} }
newResource := ctx.NewResource()
if imageVerify.Required && !reflect.DeepEqual(ctx.newResource, unstructured.Unstructured{}) { if imageVerify.Required && !reflect.DeepEqual(newResource, unstructured.Unstructured{}) {
verified, err := isImageVerified(ctx.newResource, image, log) verified, err := isImageVerified(newResource, image, log)
if err != nil { if err != nil {
return err return err
} }
if !verified { if !verified {
return fmt.Errorf("unverified image %s", image) return fmt.Errorf("unverified image %s", image)
} }
} }
return nil return nil
} }

View file

@ -163,7 +163,7 @@ var cfg = config.NewDefaultConfiguration()
func doVerifyAndPatchImages( func doVerifyAndPatchImages(
ctx context.Context, ctx context.Context,
rclient registryclient.Client, rclient registryclient.Client,
pContext *PolicyContext, pContext engineapi.PolicyContext,
cfg config.Configuration, cfg config.Configuration,
) (*engineapi.EngineResponse, *ImageVerificationMetadata) { ) (*engineapi.EngineResponse, *ImageVerificationMetadata) {
return VerifyAndPatchImages( return VerifyAndPatchImages(

View file

@ -17,13 +17,14 @@ import (
"github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/registryclient" "github.com/kyverno/kyverno/pkg/registryclient"
"github.com/pkg/errors" "github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
) )
type ContextLoaderFactory = func(pContext *PolicyContext, ruleName string) engineapi.ContextLoader type ContextLoaderFactory = func(pContext engineapi.PolicyContext, ruleName string) engineapi.ContextLoader
func LegacyContextLoaderFactory(rclient registryclient.Client) ContextLoaderFactory { func LegacyContextLoaderFactory(rclient registryclient.Client) ContextLoaderFactory {
if store.IsMock() { if store.IsMock() {
return func(pContext *PolicyContext, ruleName string) engineapi.ContextLoader { return func(pContext engineapi.PolicyContext, ruleName string) engineapi.ContextLoader {
policy := pContext.Policy() policy := pContext.Policy()
return &mockContextLoader{ return &mockContextLoader{
logger: logging.WithName("MockContextLoaderFactory"), logger: logging.WithName("MockContextLoaderFactory"),
@ -31,16 +32,16 @@ func LegacyContextLoaderFactory(rclient registryclient.Client) ContextLoaderFact
ruleName: ruleName, ruleName: ruleName,
client: pContext.Client(), client: pContext.Client(),
rclient: rclient, rclient: rclient,
cmResolver: pContext.informerCacheResolvers, cmResolver: pContext.ResolveConfigMap,
} }
} }
} }
return func(pContext *PolicyContext, ruleName string) engineapi.ContextLoader { return func(pContext engineapi.PolicyContext, ruleName string) engineapi.ContextLoader {
return &contextLoader{ return &contextLoader{
logger: logging.WithName("LegacyContextLoaderFactory"), logger: logging.WithName("LegacyContextLoaderFactory"),
client: pContext.Client(), client: pContext.Client(),
rclient: rclient, rclient: rclient,
cmResolver: pContext.informerCacheResolvers, cmResolver: pContext.ResolveConfigMap,
} }
} }
} }
@ -49,7 +50,7 @@ type contextLoader struct {
logger logr.Logger logger logr.Logger
rclient registryclient.Client rclient registryclient.Client
client dclient.Interface client dclient.Interface
cmResolver engineapi.ConfigmapResolver cmResolver func(context.Context, string, string) (*corev1.ConfigMap, error)
} }
func (l *contextLoader) Load(ctx context.Context, contextEntries []kyvernov1.ContextEntry, enginectx enginecontext.Interface) error { func (l *contextLoader) Load(ctx context.Context, contextEntries []kyvernov1.ContextEntry, enginectx enginecontext.Interface) error {
@ -81,7 +82,7 @@ type mockContextLoader struct {
ruleName string ruleName string
rclient registryclient.Client rclient registryclient.Client
client dclient.Interface client dclient.Interface
cmResolver engineapi.ConfigmapResolver cmResolver func(context.Context, string, string) (*corev1.ConfigMap, error)
} }
func (l *mockContextLoader) Load(ctx context.Context, contextEntries []kyvernov1.ContextEntry, enginectx enginecontext.Interface) error { func (l *mockContextLoader) Load(ctx context.Context, contextEntries []kyvernov1.ContextEntry, enginectx enginecontext.Interface) error {
@ -122,7 +123,7 @@ func (l *mockContextLoader) Load(ctx context.Context, contextEntries []kyvernov1
return nil return nil
} }
func LoadContext(ctx context.Context, factory ContextLoaderFactory, contextEntries []kyvernov1.ContextEntry, pContext *PolicyContext, ruleName string) error { func LoadContext(ctx context.Context, factory ContextLoaderFactory, contextEntries []kyvernov1.ContextEntry, pContext engineapi.PolicyContext, ruleName string) error {
return factory(pContext, ruleName).Load(ctx, contextEntries, pContext.JSONContext()) return factory(pContext, ruleName).Load(ctx, contextEntries, pContext.JSONContext())
} }
@ -300,7 +301,7 @@ func applyJMESPath(jmesPath string, data interface{}) (interface{}, error) {
return jp.Search(data) return jp.Search(data)
} }
func loadConfigMap(ctx context.Context, logger logr.Logger, entry kyvernov1.ContextEntry, enginectx enginecontext.Interface, resolver engineapi.ConfigmapResolver) error { func loadConfigMap(ctx context.Context, logger logr.Logger, entry kyvernov1.ContextEntry, enginectx enginecontext.Interface, resolver func(context.Context, string, string) (*corev1.ConfigMap, error)) error {
data, err := fetchConfigMap(ctx, logger, entry, enginectx, resolver) data, err := fetchConfigMap(ctx, logger, entry, enginectx, resolver)
if err != nil { if err != nil {
return fmt.Errorf("failed to retrieve config map for context entry %s: %v", entry.Name, err) return fmt.Errorf("failed to retrieve config map for context entry %s: %v", entry.Name, err)
@ -312,7 +313,7 @@ func loadConfigMap(ctx context.Context, logger logr.Logger, entry kyvernov1.Cont
return nil return nil
} }
func fetchConfigMap(ctx context.Context, logger logr.Logger, entry kyvernov1.ContextEntry, enginectx enginecontext.Interface, resolver engineapi.ConfigmapResolver) ([]byte, error) { func fetchConfigMap(ctx context.Context, logger logr.Logger, entry kyvernov1.ContextEntry, enginectx enginecontext.Interface, resolver func(context.Context, string, string) (*corev1.ConfigMap, error)) ([]byte, error) {
contextData := make(map[string]interface{}) contextData := make(map[string]interface{})
name, err := variables.SubstituteAll(logger, enginectx, entry.ConfigMap.Name) name, err := variables.SubstituteAll(logger, enginectx, entry.ConfigMap.Name)
@ -329,7 +330,7 @@ func fetchConfigMap(ctx context.Context, logger logr.Logger, entry kyvernov1.Con
namespace = "default" namespace = "default"
} }
obj, err := resolver.Get(ctx, namespace.(string), name.(string)) obj, err := resolver(ctx, namespace.(string), name.(string))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get configmap %s/%s : %v", namespace, name, err) return nil, fmt.Errorf("failed to get configmap %s/%s : %v", namespace, name, err)
} }

View file

@ -35,7 +35,7 @@ const (
//go:embed resources/default-config.yaml //go:embed resources/default-config.yaml
var defaultConfigBytes []byte var defaultConfigBytes []byte
func processYAMLValidationRule(log logr.Logger, ctx *PolicyContext, rule *kyvernov1.Rule) *engineapi.RuleResponse { func processYAMLValidationRule(log logr.Logger, ctx engineapi.PolicyContext, rule *kyvernov1.Rule) *engineapi.RuleResponse {
if isDeleteRequest(ctx) { if isDeleteRequest(ctx) {
return nil return nil
} }
@ -43,7 +43,7 @@ func processYAMLValidationRule(log logr.Logger, ctx *PolicyContext, rule *kyvern
return ruleResp return ruleResp
} }
func handleVerifyManifest(ctx *PolicyContext, rule *kyvernov1.Rule, logger logr.Logger) *engineapi.RuleResponse { func handleVerifyManifest(ctx engineapi.PolicyContext, rule *kyvernov1.Rule, logger logr.Logger) *engineapi.RuleResponse {
verified, reason, err := verifyManifest(ctx, *rule.Validation.Manifests, logger) verified, reason, err := verifyManifest(ctx, *rule.Validation.Manifests, logger)
if err != nil { if err != nil {
logger.V(3).Info("verifyManifest return err", "error", err.Error()) logger.V(3).Info("verifyManifest return err", "error", err.Error())
@ -56,9 +56,9 @@ func handleVerifyManifest(ctx *PolicyContext, rule *kyvernov1.Rule, logger logr.
return ruleResponse(*rule, engineapi.Validation, reason, engineapi.RuleStatusPass) return ruleResponse(*rule, engineapi.Validation, reason, engineapi.RuleStatusPass)
} }
func verifyManifest(policyContext *PolicyContext, verifyRule kyvernov1.Manifests, logger logr.Logger) (bool, string, error) { func verifyManifest(policyContext engineapi.PolicyContext, verifyRule kyvernov1.Manifests, logger logr.Logger) (bool, string, error) {
// load AdmissionRequest // load AdmissionRequest
request, err := policyContext.jsonContext.Query("request") request, err := policyContext.JSONContext().Query("request")
if err != nil { if err != nil {
return false, "", errors.Wrapf(err, "failed to get a request from policyContext") return false, "", errors.Wrapf(err, "failed to get a request from policyContext")
} }
@ -106,7 +106,7 @@ func verifyManifest(policyContext *PolicyContext, verifyRule kyvernov1.Manifests
} }
if !vo.DisableDryRun { if !vo.DisableDryRun {
// check if kyverno can 'create' dryrun resource // check if kyverno can 'create' dryrun resource
ok, err := checkDryRunPermission(policyContext.client, adreq.Kind.Kind, vo.DryRunNamespace) ok, err := checkDryRunPermission(policyContext.Client(), adreq.Kind.Kind, vo.DryRunNamespace)
if err != nil { if err != nil {
logger.V(1).Info("failed to check permissions to 'create' resource. disabled DryRun option.", "dryrun namespace", vo.DryRunNamespace, "kind", adreq.Kind.Kind, "error", err.Error()) logger.V(1).Info("failed to check permissions to 'create' resource. disabled DryRun option.", "dryrun namespace", vo.DryRunNamespace, "kind", adreq.Kind.Kind, "error", err.Error())
vo.DisableDryRun = true vo.DisableDryRun = true

View file

@ -7,6 +7,7 @@ import (
"github.com/go-logr/logr" "github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/engine/variables"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/kyverno/kyverno/pkg/utils/wildcard" "github.com/kyverno/kyverno/pkg/utils/wildcard"
@ -23,7 +24,7 @@ type resourceInfo struct {
parentResourceGVR metav1.GroupVersionResource parentResourceGVR metav1.GroupVersionResource
} }
func loadTargets(targets []kyvernov1.ResourceSpec, ctx *PolicyContext, logger logr.Logger) ([]resourceInfo, error) { func loadTargets(targets []kyvernov1.ResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) ([]resourceInfo, error) {
var targetObjects []resourceInfo var targetObjects []resourceInfo
var errors []error var errors []error
@ -46,23 +47,23 @@ func loadTargets(targets []kyvernov1.ResourceSpec, ctx *PolicyContext, logger lo
return targetObjects, multierr.Combine(errors...) return targetObjects, multierr.Combine(errors...)
} }
func resolveSpec(i int, target kyvernov1.ResourceSpec, ctx *PolicyContext, logger logr.Logger) (kyvernov1.ResourceSpec, error) { func resolveSpec(i int, target kyvernov1.ResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) (kyvernov1.ResourceSpec, error) {
kind, err := variables.SubstituteAll(logger, ctx.jsonContext, target.Kind) kind, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.Kind)
if err != nil { if err != nil {
return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Kind %s: %v", i, target.Kind, err) return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Kind %s: %v", i, target.Kind, err)
} }
apiversion, err := variables.SubstituteAll(logger, ctx.jsonContext, target.APIVersion) apiversion, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.APIVersion)
if err != nil { if err != nil {
return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].APIVersion %s: %v", i, target.APIVersion, err) return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].APIVersion %s: %v", i, target.APIVersion, err)
} }
namespace, err := variables.SubstituteAll(logger, ctx.jsonContext, target.Namespace) namespace, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.Namespace)
if err != nil { if err != nil {
return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Namespace %s: %v", i, target.Namespace, err) return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Namespace %s: %v", i, target.Namespace, err)
} }
name, err := variables.SubstituteAll(logger, ctx.jsonContext, target.Name) name, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.Name)
if err != nil { if err != nil {
return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Name %s: %v", i, target.Name, err) return kyvernov1.ResourceSpec{}, fmt.Errorf("failed to substitute variables in target[%d].Name %s: %v", i, target.Name, err)
} }
@ -75,17 +76,18 @@ func resolveSpec(i int, target kyvernov1.ResourceSpec, ctx *PolicyContext, logge
}, nil }, nil
} }
func getTargets(target kyvernov1.ResourceSpec, ctx *PolicyContext) ([]resourceInfo, error) { func getTargets(target kyvernov1.ResourceSpec, ctx engineapi.PolicyContext) ([]resourceInfo, error) {
var targetObjects []resourceInfo var targetObjects []resourceInfo
namespace := target.Namespace namespace := target.Namespace
name := target.Name name := target.Name
policy := ctx.Policy()
// if it's namespaced policy, targets has to be loaded only from the policy's namespace // if it's namespaced policy, targets has to be loaded only from the policy's namespace
if ctx.policy.IsNamespaced() { if policy.IsNamespaced() {
namespace = ctx.policy.GetNamespace() namespace = policy.GetNamespace()
} }
client := ctx.Client()
apiResource, parentAPIResource, _, err := ctx.client.Discovery().FindResource(target.APIVersion, target.Kind) apiResource, parentAPIResource, _, err := client.Discovery().FindResource(target.APIVersion, target.Kind)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -102,14 +104,14 @@ func getTargets(target kyvernov1.ResourceSpec, ctx *PolicyContext) ([]resourceIn
Version: parentAPIResource.Version, Version: parentAPIResource.Version,
}.String() }.String()
subresourceName = strings.Split(apiResource.Name, "/")[1] subresourceName = strings.Split(apiResource.Name, "/")[1]
obj, err = ctx.client.GetResource(context.TODO(), apiVersion, parentAPIResource.Kind, namespace, name, subresourceName) obj, err = client.GetResource(context.TODO(), apiVersion, parentAPIResource.Kind, namespace, name, subresourceName)
parentResourceGVR = metav1.GroupVersionResource{ parentResourceGVR = metav1.GroupVersionResource{
Group: parentAPIResource.Group, Group: parentAPIResource.Group,
Version: parentAPIResource.Version, Version: parentAPIResource.Version,
Resource: parentAPIResource.Name, Resource: parentAPIResource.Name,
} }
} else { } else {
obj, err = ctx.client.GetResource(context.TODO(), target.APIVersion, target.Kind, namespace, name) obj, err = client.GetResource(context.TODO(), target.APIVersion, target.Kind, namespace, name)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get target %s/%s %s/%s : %v", target.APIVersion, target.Kind, namespace, name, err) return nil, fmt.Errorf("failed to get target %s/%s %s/%s : %v", target.APIVersion, target.Kind, namespace, name, err)
@ -123,7 +125,7 @@ func getTargets(target kyvernov1.ResourceSpec, ctx *PolicyContext) ([]resourceIn
Group: parentAPIResource.Group, Group: parentAPIResource.Group,
Version: parentAPIResource.Version, Version: parentAPIResource.Version,
}.String() }.String()
objList, err := ctx.client.ListResource(context.TODO(), apiVersion, parentAPIResource.Kind, "", nil) objList, err := client.ListResource(context.TODO(), apiVersion, parentAPIResource.Kind, "", nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -138,7 +140,7 @@ func getTargets(target kyvernov1.ResourceSpec, ctx *PolicyContext) ([]resourceIn
for i := range parentObjects { for i := range parentObjects {
parentObj := parentObjects[i] parentObj := parentObjects[i]
subresourceName := strings.Split(apiResource.Name, "/")[1] subresourceName := strings.Split(apiResource.Name, "/")[1]
obj, err := ctx.client.GetResource(context.TODO(), parentObj.GetAPIVersion(), parentAPIResource.Kind, parentObj.GetNamespace(), parentObj.GetName(), subresourceName) obj, err := client.GetResource(context.TODO(), parentObj.GetAPIVersion(), parentAPIResource.Kind, parentObj.GetNamespace(), parentObj.GetName(), subresourceName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -151,7 +153,7 @@ func getTargets(target kyvernov1.ResourceSpec, ctx *PolicyContext) ([]resourceIn
} }
} else { } else {
// list all targets if wildcard is specified // list all targets if wildcard is specified
objList, err := ctx.client.ListResource(context.TODO(), target.APIVersion, target.Kind, "", nil) objList, err := client.ListResource(context.TODO(), target.APIVersion, target.Kind, "", nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -24,15 +24,15 @@ import (
func Mutate( func Mutate(
ctx context.Context, ctx context.Context,
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
) (resp *engineapi.EngineResponse) { ) (resp *engineapi.EngineResponse) {
startTime := time.Now() startTime := time.Now()
policy := policyContext.policy policy := policyContext.Policy()
resp = &engineapi.EngineResponse{ resp = &engineapi.EngineResponse{
Policy: policy, Policy: policy,
} }
matchedResource := policyContext.newResource matchedResource := policyContext.NewResource()
enginectx := policyContext.jsonContext enginectx := policyContext.JSONContext()
var skippedRules []string var skippedRules []string
logger := logging.WithName("EngineMutate").WithValues("policy", policy.GetName(), "kind", matchedResource.GetKind(), logger := logging.WithName("EngineMutate").WithValues("policy", policy.GetName(), "kind", matchedResource.GetKind(),
@ -43,8 +43,8 @@ func Mutate(
startMutateResultResponse(resp, policy, matchedResource) startMutateResultResponse(resp, policy, matchedResource)
defer endMutateResultResponse(logger, resp, startTime) defer endMutateResultResponse(logger, resp, startTime)
policyContext.jsonContext.Checkpoint() policyContext.JSONContext().Checkpoint()
defer policyContext.jsonContext.Restore() defer policyContext.JSONContext().Restore()
var err error var err error
applyRules := policy.GetSpec().GetApplyRules() applyRules := policy.GetSpec().GetApplyRules()
@ -62,13 +62,13 @@ func Mutate(
func(ctx context.Context, span trace.Span) { func(ctx context.Context, span trace.Span) {
logger := logger.WithValues("rule", rule.Name) logger := logger.WithValues("rule", rule.Name)
var excludeResource []string var excludeResource []string
if len(policyContext.excludeGroupRole) > 0 { if len(policyContext.ExcludeGroupRole()) > 0 {
excludeResource = policyContext.excludeGroupRole excludeResource = policyContext.ExcludeGroupRole()
} }
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...) kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, policyContext) subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, policyContext)
if err = MatchesResourceDescription(subresourceGVKToAPIResource, matchedResource, rule, policyContext.admissionInfo, excludeResource, policyContext.namespaceLabels, policyContext.policy.GetNamespace(), policyContext.subresource); err != nil { if err = MatchesResourceDescription(subresourceGVKToAPIResource, matchedResource, rule, policyContext.AdmissionInfo(), excludeResource, policyContext.NamespaceLabels(), policyContext.Policy().GetNamespace(), policyContext.SubResource()); err != nil {
logger.V(4).Info("rule not matched", "reason", err.Error()) logger.V(4).Info("rule not matched", "reason", err.Error())
skippedRules = append(skippedRules, rule.Name) skippedRules = append(skippedRules, rule.Name)
return return
@ -82,8 +82,8 @@ func Mutate(
} }
logger.V(3).Info("processing mutate rule", "applyRules", applyRules) logger.V(3).Info("processing mutate rule", "applyRules", applyRules)
resource, err := policyContext.jsonContext.Query("request.object") resource, err := policyContext.JSONContext().Query("request.object")
policyContext.jsonContext.Reset() policyContext.JSONContext().Reset()
if err == nil && resource != nil { if err == nil && resource != nil {
if err := enginectx.AddResource(resource.(map[string]interface{})); err != nil { if err := enginectx.AddResource(resource.(map[string]interface{})); err != nil {
logger.Error(err, "unable to update resource object") logger.Error(err, "unable to update resource object")
@ -103,7 +103,7 @@ func Mutate(
ruleCopy := rule.DeepCopy() ruleCopy := rule.DeepCopy()
var patchedResources []resourceInfo var patchedResources []resourceInfo
if !policyContext.admissionOperation && rule.IsMutateExisting() { if !policyContext.AdmissionOperation() && rule.IsMutateExisting() {
targets, err := loadTargets(ruleCopy.Mutation.Targets, policyContext, logger) targets, err := loadTargets(ruleCopy.Mutation.Targets, policyContext, logger)
if err != nil { if err != nil {
rr := ruleResponse(rule, engineapi.Mutation, err.Error(), engineapi.RuleStatusError) rr := ruleResponse(rule, engineapi.Mutation, err.Error(), engineapi.RuleStatusError)
@ -113,12 +113,12 @@ func Mutate(
} }
} else { } else {
var parentResourceGVR metav1.GroupVersionResource var parentResourceGVR metav1.GroupVersionResource
if policyContext.subresource != "" { if policyContext.SubResource() != "" {
parentResourceGVR = policyContext.requestResource parentResourceGVR = policyContext.RequestResource()
} }
patchedResources = append(patchedResources, resourceInfo{ patchedResources = append(patchedResources, resourceInfo{
unstructured: matchedResource, unstructured: matchedResource,
subresource: policyContext.subresource, subresource: policyContext.SubResource(),
parentResourceGVR: parentResourceGVR, parentResourceGVR: parentResourceGVR,
}) })
} }
@ -128,9 +128,9 @@ func Mutate(
continue continue
} }
if !policyContext.admissionOperation && rule.IsMutateExisting() { if !policyContext.AdmissionOperation() && rule.IsMutateExisting() {
policyContext := policyContext.Copy() policyContext := policyContext.Copy()
if err := policyContext.jsonContext.AddTargetResource(patchedResource.unstructured.Object); err != nil { if err := policyContext.JSONContext().AddTargetResource(patchedResource.unstructured.Object); err != nil {
logging.Error(err, "failed to add target resource to the context") logging.Error(err, "failed to add target resource to the context")
continue continue
} }
@ -186,7 +186,7 @@ func Mutate(
return resp return resp
} }
func mutateResource(rule *kyvernov1.Rule, ctx *PolicyContext, resource unstructured.Unstructured, logger logr.Logger) *mutate.Response { func mutateResource(rule *kyvernov1.Rule, ctx engineapi.PolicyContext, resource unstructured.Unstructured, logger logr.Logger) *mutate.Response {
preconditionsPassed, err := checkPreconditions(logger, ctx, rule.GetAnyAllConditions()) preconditionsPassed, err := checkPreconditions(logger, ctx, rule.GetAnyAllConditions())
if err != nil { if err != nil {
return mutate.NewErrorResponse("failed to evaluate preconditions", err) return mutate.NewErrorResponse("failed to evaluate preconditions", err)
@ -201,7 +201,7 @@ func mutateResource(rule *kyvernov1.Rule, ctx *PolicyContext, resource unstructu
type forEachMutator struct { type forEachMutator struct {
rule *kyvernov1.Rule rule *kyvernov1.Rule
policyContext *PolicyContext policyContext engineapi.PolicyContext
foreach []kyvernov1.ForEachMutation foreach []kyvernov1.ForEachMutation
resource resourceInfo resource resourceInfo
nesting int nesting int

View file

@ -1,25 +1,25 @@
package engine package engine
import ( import (
"context"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1" kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine/api" engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginectx "github.com/kyverno/kyverno/pkg/engine/context" enginectx "github.com/kyverno/kyverno/pkg/engine/context"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
"github.com/pkg/errors" "github.com/pkg/errors"
admissionv1 "k8s.io/api/admission/v1" admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
) )
// ExcludeFunc is a function used to determine if a resource is excluded
type ExcludeFunc = func(kind, namespace, name string) bool
type PolicyExceptionLister interface { type PolicyExceptionLister interface {
// List lists all PolicyExceptions in the indexer. // List lists all PolicyExceptions in the indexer.
// Objects returned here must be treated as read-only. // Objects returned here must be treated as read-only.
@ -59,7 +59,7 @@ type PolicyContext struct {
// Config handler // Config handler
excludeGroupRole []string excludeGroupRole []string
excludeResourceFunc ExcludeFunc excludeResourceFunc engineapi.ExcludeFunc
// jsonContext is the variable context // jsonContext is the variable context
jsonContext enginectx.Interface jsonContext enginectx.Interface
@ -71,7 +71,7 @@ type PolicyContext struct {
admissionOperation bool admissionOperation bool
// informerCacheResolvers - used to get resources from informer cache // informerCacheResolvers - used to get resources from informer cache
informerCacheResolvers api.ConfigmapResolver informerCacheResolvers engineapi.ConfigmapResolver
// subresource is the subresource being requested, if any (for example, "status" or "scale") // subresource is the subresource being requested, if any (for example, "status" or "scale")
subresource string subresource string
@ -79,16 +79,13 @@ type PolicyContext struct {
// subresourcesInPolicy represents the APIResources that are subresources along with their parent resource. // subresourcesInPolicy represents the APIResources that are subresources along with their parent resource.
// This is used to determine if a resource is a subresource. It is only used when the policy context is populated // This is used to determine if a resource is a subresource. It is only used when the policy context is populated
// by kyverno CLI. In all other cases when connected to a cluster, this is empty. // by kyverno CLI. In all other cases when connected to a cluster, this is empty.
subresourcesInPolicy []struct { subresourcesInPolicy []engineapi.SubResource
APIResource metav1.APIResource
ParentResource metav1.APIResource
}
// peLister list all policy exceptions // peLister list all policy exceptions
peLister PolicyExceptionLister peLister PolicyExceptionLister
} }
// Getters // engineapi.PolicyContext interface
func (c *PolicyContext) Policy() kyvernov1.PolicyInterface { func (c *PolicyContext) Policy() kyvernov1.PolicyInterface {
return c.policy return c.policy
@ -106,10 +103,50 @@ func (c *PolicyContext) AdmissionInfo() kyvernov1beta1.RequestInfo {
return c.admissionInfo return c.admissionInfo
} }
func (c *PolicyContext) NamespaceLabels() map[string]string {
return c.namespaceLabels
}
func (c *PolicyContext) SubResource() string {
return c.subresource
}
func (c *PolicyContext) SubresourcesInPolicy() []engineapi.SubResource {
return c.subresourcesInPolicy
}
func (c *PolicyContext) ExcludeGroupRole() []string {
return c.excludeGroupRole
}
func (c *PolicyContext) AdmissionOperation() bool {
return c.admissionOperation
}
func (c *PolicyContext) RequestResource() metav1.GroupVersionResource {
return c.requestResource
}
func (c *PolicyContext) Element() unstructured.Unstructured {
return c.element
}
func (c *PolicyContext) SetElement(element unstructured.Unstructured) {
c.element = element
}
func (c *PolicyContext) JSONContext() enginectx.Interface { func (c *PolicyContext) JSONContext() enginectx.Interface {
return c.jsonContext return c.jsonContext
} }
func (c *PolicyContext) Client() dclient.Interface {
return c.client
}
func (c PolicyContext) Copy() engineapi.PolicyContext {
return c.copy()
}
func (c *PolicyContext) FindExceptions(rule string) ([]*kyvernov2alpha1.PolicyException, error) { func (c *PolicyContext) FindExceptions(rule string) ([]*kyvernov2alpha1.PolicyException, error) {
if c.peLister == nil { if c.peLister == nil {
return nil, nil return nil, nil
@ -131,44 +168,48 @@ func (c *PolicyContext) FindExceptions(rule string) ([]*kyvernov2alpha1.PolicyEx
return result, nil return result, nil
} }
func (c *PolicyContext) Client() dclient.Interface { func (c *PolicyContext) ExcludeResourceFunc() engineapi.ExcludeFunc {
return c.client return c.excludeResourceFunc
}
func (c *PolicyContext) ResolveConfigMap(ctx context.Context, namespace string, name string) (*corev1.ConfigMap, error) {
return c.informerCacheResolvers.Get(ctx, namespace, name)
} }
// Mutators // Mutators
func (c *PolicyContext) WithPolicy(policy kyvernov1.PolicyInterface) *PolicyContext { func (c *PolicyContext) WithPolicy(policy kyvernov1.PolicyInterface) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.policy = policy copy.policy = policy
return copy return copy
} }
func (c *PolicyContext) WithNamespaceLabels(namespaceLabels map[string]string) *PolicyContext { func (c *PolicyContext) WithNamespaceLabels(namespaceLabels map[string]string) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.namespaceLabels = namespaceLabels copy.namespaceLabels = namespaceLabels
return copy return copy
} }
func (c *PolicyContext) WithAdmissionInfo(admissionInfo kyvernov1beta1.RequestInfo) *PolicyContext { func (c *PolicyContext) WithAdmissionInfo(admissionInfo kyvernov1beta1.RequestInfo) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.admissionInfo = admissionInfo copy.admissionInfo = admissionInfo
return copy return copy
} }
func (c *PolicyContext) WithRequestResource(requestResource metav1.GroupVersionResource) *PolicyContext { func (c *PolicyContext) WithRequestResource(requestResource metav1.GroupVersionResource) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.requestResource = requestResource copy.requestResource = requestResource
return copy return copy
} }
func (c *PolicyContext) WithNewResource(resource unstructured.Unstructured) *PolicyContext { func (c *PolicyContext) WithNewResource(resource unstructured.Unstructured) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.newResource = resource copy.newResource = resource
return copy return copy
} }
func (c *PolicyContext) WithOldResource(resource unstructured.Unstructured) *PolicyContext { func (c *PolicyContext) WithOldResource(resource unstructured.Unstructured) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.oldResource = resource copy.oldResource = resource
return copy return copy
} }
@ -178,19 +219,19 @@ func (c *PolicyContext) WithResources(newResource unstructured.Unstructured, old
} }
func (c *PolicyContext) WithClient(client dclient.Interface) *PolicyContext { func (c *PolicyContext) WithClient(client dclient.Interface) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.client = client copy.client = client
return copy return copy
} }
func (c *PolicyContext) WithExcludeGroupRole(excludeGroupRole ...string) *PolicyContext { func (c *PolicyContext) WithExcludeGroupRole(excludeGroupRole ...string) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.excludeGroupRole = excludeGroupRole copy.excludeGroupRole = excludeGroupRole
return copy return copy
} }
func (c *PolicyContext) WithExcludeResourceFunc(excludeResourceFunc ExcludeFunc) *PolicyContext { func (c *PolicyContext) WithExcludeResourceFunc(excludeResourceFunc engineapi.ExcludeFunc) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.excludeResourceFunc = excludeResourceFunc copy.excludeResourceFunc = excludeResourceFunc
return copy return copy
} }
@ -200,39 +241,39 @@ func (c *PolicyContext) WithConfiguration(configuration config.Configuration) *P
} }
func (c *PolicyContext) WithAdmissionOperation(admissionOperation bool) *PolicyContext { func (c *PolicyContext) WithAdmissionOperation(admissionOperation bool) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.admissionOperation = admissionOperation copy.admissionOperation = admissionOperation
return copy return copy
} }
func (c *PolicyContext) WithInformerCacheResolver(informerCacheResolver api.ConfigmapResolver) *PolicyContext { func (c *PolicyContext) WithInformerCacheResolver(informerCacheResolver engineapi.ConfigmapResolver) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.informerCacheResolvers = informerCacheResolver copy.informerCacheResolvers = informerCacheResolver
return copy return copy
} }
func (c *PolicyContext) WithSubresource(subresource string) *PolicyContext { func (c *PolicyContext) WithSubresource(subresource string) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.subresource = subresource copy.subresource = subresource
return copy return copy
} }
func (c *PolicyContext) WithSubresourcesInPolicy(subresourcesInPolicy []struct { func (c *PolicyContext) WithSubresourcesInPolicy(subresourcesInPolicy []engineapi.SubResource) *PolicyContext {
APIResource metav1.APIResource copy := c.copy()
ParentResource metav1.APIResource
},
) *PolicyContext {
copy := c.Copy()
copy.subresourcesInPolicy = subresourcesInPolicy copy.subresourcesInPolicy = subresourcesInPolicy
return copy return copy
} }
func (c *PolicyContext) WithExceptions(peLister PolicyExceptionLister) *PolicyContext { func (c *PolicyContext) WithExceptions(peLister PolicyExceptionLister) *PolicyContext {
copy := c.Copy() copy := c.copy()
copy.peLister = peLister copy.peLister = peLister
return copy return copy
} }
func (c PolicyContext) copy() *PolicyContext {
return &c
}
// Constructors // Constructors
func NewPolicyContextWithJsonContext(jsonContext enginectx.Interface) *PolicyContext { func NewPolicyContextWithJsonContext(jsonContext enginectx.Interface) *PolicyContext {
return &PolicyContext{ return &PolicyContext{
@ -253,7 +294,7 @@ func NewPolicyContextFromAdmissionRequest(
admissionInfo kyvernov1beta1.RequestInfo, admissionInfo kyvernov1beta1.RequestInfo,
configuration config.Configuration, configuration config.Configuration,
client dclient.Interface, client dclient.Interface,
informerCacheResolver api.ConfigmapResolver, informerCacheResolver engineapi.ConfigmapResolver,
polexLister PolicyExceptionLister, polexLister PolicyExceptionLister,
) (*PolicyContext, error) { ) (*PolicyContext, error) {
ctx, err := newVariablesContext(request, &admissionInfo) ctx, err := newVariablesContext(request, &admissionInfo)
@ -282,10 +323,6 @@ func NewPolicyContextFromAdmissionRequest(
return policyContext, nil return policyContext, nil
} }
func (c PolicyContext) Copy() *PolicyContext {
return &c
}
func newVariablesContext(request *admissionv1.AdmissionRequest, userRequestInfo *kyvernov1beta1.RequestInfo) (enginectx.Interface, error) { func newVariablesContext(request *admissionv1.AdmissionRequest, userRequestInfo *kyvernov1beta1.RequestInfo) (enginectx.Interface, error) {
ctx := enginectx.NewContext() ctx := enginectx.NewContext()
if err := ctx.AddRequest(request); err != nil { if err := ctx.AddRequest(request); err != nil {

View file

@ -325,8 +325,8 @@ func ManagedPodResource(policy kyvernov1.PolicyInterface, resource unstructured.
return false return false
} }
func checkPreconditions(logger logr.Logger, ctx *PolicyContext, anyAllConditions apiextensions.JSON) (bool, error) { func checkPreconditions(logger logr.Logger, ctx engineapi.PolicyContext, anyAllConditions apiextensions.JSON) (bool, error) {
preconditions, err := variables.SubstituteAllInPreconditions(logger, ctx.jsonContext, anyAllConditions) preconditions, err := variables.SubstituteAllInPreconditions(logger, ctx.JSONContext(), anyAllConditions)
if err != nil { if err != nil {
return false, errors.Wrapf(err, "failed to substitute variables in preconditions") return false, errors.Wrapf(err, "failed to substitute variables in preconditions")
} }
@ -336,7 +336,7 @@ func checkPreconditions(logger logr.Logger, ctx *PolicyContext, anyAllConditions
return false, errors.Wrapf(err, "failed to parse preconditions") return false, errors.Wrapf(err, "failed to parse preconditions")
} }
pass := variables.EvaluateConditions(logger, ctx.jsonContext, typeConditions) pass := variables.EvaluateConditions(logger, ctx.JSONContext(), typeConditions)
return pass, nil return pass, nil
} }

View file

@ -39,7 +39,7 @@ import (
func Validate( func Validate(
ctx context.Context, ctx context.Context,
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
cfg config.Configuration, cfg config.Configuration,
) (resp *engineapi.EngineResponse) { ) (resp *engineapi.EngineResponse) {
resp = &engineapi.EngineResponse{} resp = &engineapi.EngineResponse{}
@ -53,46 +53,48 @@ func Validate(
}() }()
resp = validateResource(ctx, contextLoader, logger, policyContext, cfg) resp = validateResource(ctx, contextLoader, logger, policyContext, cfg)
resp.NamespaceLabels = policyContext.namespaceLabels resp.NamespaceLabels = policyContext.NamespaceLabels()
return return
} }
func buildLogger(ctx *PolicyContext) logr.Logger { func buildLogger(ctx engineapi.PolicyContext) logr.Logger {
logger := logging.WithName("EngineValidate").WithValues("policy", ctx.policy.GetName()) logger := logging.WithName("EngineValidate").WithValues("policy", ctx.Policy().GetName())
if reflect.DeepEqual(ctx.newResource, unstructured.Unstructured{}) { newResource := ctx.NewResource()
logger = logger.WithValues("kind", ctx.oldResource.GetKind(), "namespace", ctx.oldResource.GetNamespace(), "name", ctx.oldResource.GetName()) oldResource := ctx.OldResource()
if reflect.DeepEqual(newResource, unstructured.Unstructured{}) {
logger = logger.WithValues("kind", oldResource.GetKind(), "namespace", oldResource.GetNamespace(), "name", oldResource.GetName())
} else { } else {
logger = logger.WithValues("kind", ctx.newResource.GetKind(), "namespace", ctx.newResource.GetNamespace(), "name", ctx.newResource.GetName()) logger = logger.WithValues("kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName())
} }
return logger return logger
} }
func buildResponse(ctx *PolicyContext, resp *engineapi.EngineResponse, startTime time.Time) { func buildResponse(ctx engineapi.PolicyContext, resp *engineapi.EngineResponse, startTime time.Time) {
if reflect.DeepEqual(resp, engineapi.EngineResponse{}) { if reflect.DeepEqual(resp, engineapi.EngineResponse{}) {
return return
} }
if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) { if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) {
// for delete requests patched resource will be oldResource since newResource is empty // for delete requests patched resource will be oldResource since newResource is empty
resource := ctx.newResource resource := ctx.NewResource()
if reflect.DeepEqual(ctx.newResource, unstructured.Unstructured{}) { if reflect.DeepEqual(resource, unstructured.Unstructured{}) {
resource = ctx.oldResource resource = ctx.OldResource()
} }
resp.PatchedResource = resource resp.PatchedResource = resource
} }
policy := ctx.Policy()
resp.Policy = ctx.policy resp.Policy = policy
resp.PolicyResponse.Policy.Name = ctx.policy.GetName() resp.PolicyResponse.Policy.Name = policy.GetName()
resp.PolicyResponse.Policy.Namespace = ctx.policy.GetNamespace() resp.PolicyResponse.Policy.Namespace = policy.GetNamespace()
resp.PolicyResponse.Resource.Name = resp.PatchedResource.GetName() resp.PolicyResponse.Resource.Name = resp.PatchedResource.GetName()
resp.PolicyResponse.Resource.Namespace = resp.PatchedResource.GetNamespace() resp.PolicyResponse.Resource.Namespace = resp.PatchedResource.GetNamespace()
resp.PolicyResponse.Resource.Kind = resp.PatchedResource.GetKind() resp.PolicyResponse.Resource.Kind = resp.PatchedResource.GetKind()
resp.PolicyResponse.Resource.APIVersion = resp.PatchedResource.GetAPIVersion() resp.PolicyResponse.Resource.APIVersion = resp.PatchedResource.GetAPIVersion()
resp.PolicyResponse.ValidationFailureAction = ctx.policy.GetSpec().ValidationFailureAction resp.PolicyResponse.ValidationFailureAction = policy.GetSpec().ValidationFailureAction
for _, v := range ctx.policy.GetSpec().ValidationFailureActionOverrides { for _, v := range policy.GetSpec().ValidationFailureActionOverrides {
newOverrides := engineapi.ValidationFailureActionOverride{Action: v.Action, Namespaces: v.Namespaces, NamespaceSelector: v.NamespaceSelector} newOverrides := engineapi.ValidationFailureActionOverride{Action: v.Action, Namespaces: v.Namespaces, NamespaceSelector: v.NamespaceSelector}
resp.PolicyResponse.ValidationFailureActionOverrides = append(resp.PolicyResponse.ValidationFailureActionOverrides, newOverrides) resp.PolicyResponse.ValidationFailureActionOverrides = append(resp.PolicyResponse.ValidationFailureActionOverrides, newOverrides)
} }
@ -105,24 +107,26 @@ func validateResource(
ctx context.Context, ctx context.Context,
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
log logr.Logger, log logr.Logger,
enginectx *PolicyContext, enginectx engineapi.PolicyContext,
cfg config.Configuration, cfg config.Configuration,
) *engineapi.EngineResponse { ) *engineapi.EngineResponse {
resp := &engineapi.EngineResponse{} resp := &engineapi.EngineResponse{}
enginectx.jsonContext.Checkpoint() enginectx.JSONContext().Checkpoint()
defer enginectx.jsonContext.Restore() defer enginectx.JSONContext().Restore()
rules := autogen.ComputeRules(enginectx.policy) rules := autogen.ComputeRules(enginectx.Policy())
matchCount := 0 matchCount := 0
applyRules := enginectx.policy.GetSpec().GetApplyRules() applyRules := enginectx.Policy().GetSpec().GetApplyRules()
newResource := enginectx.NewResource()
oldResource := enginectx.OldResource()
if enginectx.policy.IsNamespaced() { if enginectx.Policy().IsNamespaced() {
polNs := enginectx.policy.GetNamespace() polNs := enginectx.Policy().GetNamespace()
if enginectx.newResource.Object != nil && (enginectx.newResource.GetNamespace() != polNs || enginectx.newResource.GetNamespace() == "") { if enginectx.NewResource().Object != nil && (newResource.GetNamespace() != polNs || newResource.GetNamespace() == "") {
return resp return resp
} }
if enginectx.oldResource.Object != nil && (enginectx.oldResource.GetNamespace() != polNs || enginectx.oldResource.GetNamespace() == "") { if enginectx.OldResource().Object != nil && (oldResource.GetNamespace() != polNs || oldResource.GetNamespace() == "") {
return resp return resp
} }
} }
@ -130,7 +134,7 @@ func validateResource(
for i := range rules { for i := range rules {
rule := &rules[i] rule := &rules[i]
log.V(3).Info("processing validation rule", "matchCount", matchCount, "applyRules", applyRules) log.V(3).Info("processing validation rule", "matchCount", matchCount, "applyRules", applyRules)
enginectx.jsonContext.Reset() enginectx.JSONContext().Reset()
startTime := time.Now() startTime := time.Now()
ruleResp := tracing.ChildSpan1( ruleResp := tracing.ChildSpan1(
ctx, ctx,
@ -156,7 +160,7 @@ func validateResource(
return ruleResp return ruleResp
} }
log.V(3).Info("processing validation rule", "matchCount", matchCount, "applyRules", applyRules) log.V(3).Info("processing validation rule", "matchCount", matchCount, "applyRules", applyRules)
enginectx.jsonContext.Reset() enginectx.JSONContext().Reset()
if hasValidate && !hasYAMLSignatureVerify { if hasValidate && !hasYAMLSignatureVerify {
return processValidationRule(ctx, contextLoader, log, enginectx, rule) return processValidationRule(ctx, contextLoader, log, enginectx, rule)
} else if hasValidateImage { } else if hasValidateImage {
@ -182,7 +186,7 @@ func processValidationRule(
ctx context.Context, ctx context.Context,
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
log logr.Logger, log logr.Logger,
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
rule *kyvernov1.Rule, rule *kyvernov1.Rule,
) *engineapi.RuleResponse { ) *engineapi.RuleResponse {
v := newValidator(log, contextLoader, policyContext, rule) v := newValidator(log, contextLoader, policyContext, rule)
@ -205,7 +209,7 @@ func addRuleResponse(log logr.Logger, resp *engineapi.EngineResponse, ruleResp *
type validator struct { type validator struct {
log logr.Logger log logr.Logger
policyContext *PolicyContext policyContext engineapi.PolicyContext
rule *kyvernov1.Rule rule *kyvernov1.Rule
contextEntries []kyvernov1.ContextEntry contextEntries []kyvernov1.ContextEntry
anyAllConditions apiextensions.JSON anyAllConditions apiextensions.JSON
@ -218,7 +222,7 @@ type validator struct {
nesting int nesting int
} }
func newValidator(log logr.Logger, contextLoader ContextLoaderFactory, ctx *PolicyContext, rule *kyvernov1.Rule) *validator { func newValidator(log logr.Logger, contextLoader ContextLoaderFactory, ctx engineapi.PolicyContext, rule *kyvernov1.Rule) *validator {
ruleCopy := rule.DeepCopy() ruleCopy := rule.DeepCopy()
return &validator{ return &validator{
log: log, log: log,
@ -240,7 +244,7 @@ func newForEachValidator(
contextLoader ContextLoaderFactory, contextLoader ContextLoaderFactory,
nesting int, nesting int,
rule *kyvernov1.Rule, rule *kyvernov1.Rule,
ctx *PolicyContext, ctx engineapi.PolicyContext,
log logr.Logger, log logr.Logger,
) (*validator, error) { ) (*validator, error) {
ruleCopy := rule.DeepCopy() ruleCopy := rule.DeepCopy()
@ -325,24 +329,20 @@ func (v *validator) validateForEach(ctx context.Context) *engineapi.RuleResponse
if resp.Status != engineapi.RuleStatusPass { if resp.Status != engineapi.RuleStatusPass {
return resp return resp
} }
applyCount += count applyCount += count
} }
if applyCount == 0 { if applyCount == 0 {
if v.forEach == nil { if v.forEach == nil {
return nil return nil
} }
return ruleResponse(*v.rule, engineapi.Validation, "rule skipped", engineapi.RuleStatusSkip) return ruleResponse(*v.rule, engineapi.Validation, "rule skipped", engineapi.RuleStatusSkip)
} }
return ruleResponse(*v.rule, engineapi.Validation, "rule passed", engineapi.RuleStatusPass) return 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) { func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*engineapi.RuleResponse, int) {
v.policyContext.jsonContext.Checkpoint() v.policyContext.JSONContext().Checkpoint()
defer v.policyContext.jsonContext.Restore() defer v.policyContext.JSONContext().Restore()
applyCount := 0 applyCount := 0
for index, element := range elements { for index, element := range elements {
@ -388,7 +388,7 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
return ruleResponse(*v.rule, engineapi.Validation, "", engineapi.RuleStatusPass), applyCount return ruleResponse(*v.rule, engineapi.Validation, "", engineapi.RuleStatusPass), applyCount
} }
func addElementToContext(ctx *PolicyContext, element interface{}, index, nesting int, elementScope *bool) error { func addElementToContext(ctx engineapi.PolicyContext, element interface{}, index, nesting int, elementScope *bool) error {
data, err := variables.DocumentToUntyped(element) data, err := variables.DocumentToUntyped(element)
if err != nil { if err != nil {
return err return err
@ -412,11 +412,10 @@ func addElementToContext(ctx *PolicyContext, element interface{}, index, nesting
} }
scoped = *elementScope scoped = *elementScope
} }
if scoped { if scoped {
u := unstructured.Unstructured{} u := unstructured.Unstructured{}
u.SetUnstructuredContent(dataMap) u.SetUnstructuredContent(dataMap)
ctx.element = u ctx.SetElement(u)
} }
return nil return nil
} }
@ -437,7 +436,7 @@ func (v *validator) loadContext(ctx context.Context) error {
func (v *validator) validateDeny() *engineapi.RuleResponse { func (v *validator) validateDeny() *engineapi.RuleResponse {
anyAllCond := v.deny.GetAnyAllConditions() anyAllCond := v.deny.GetAnyAllConditions()
anyAllCond, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, anyAllCond) anyAllCond, err := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), anyAllCond)
if err != nil { if err != nil {
return ruleError(v.rule, engineapi.Validation, "failed to substitute variables in deny conditions", err) return ruleError(v.rule, engineapi.Validation, "failed to substitute variables in deny conditions", err)
} }
@ -451,7 +450,7 @@ func (v *validator) validateDeny() *engineapi.RuleResponse {
return ruleError(v.rule, engineapi.Validation, "invalid deny conditions", err) return ruleError(v.rule, engineapi.Validation, "invalid deny conditions", err)
} }
deny := variables.EvaluateConditions(v.log, v.policyContext.jsonContext, denyConditions) deny := variables.EvaluateConditions(v.log, v.policyContext.JSONContext(), denyConditions)
if deny { if deny {
return ruleResponse(*v.rule, engineapi.Validation, v.getDenyMessage(deny), engineapi.RuleStatusFail) return ruleResponse(*v.rule, engineapi.Validation, v.getDenyMessage(deny), engineapi.RuleStatusFail)
} }
@ -467,7 +466,7 @@ func (v *validator) getDenyMessage(deny bool) string {
if msg == "" { if msg == "" {
return fmt.Sprintf("validation error: rule %s failed", v.rule.Name) return fmt.Sprintf("validation error: rule %s failed", v.rule.Name)
} }
raw, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, msg) raw, err := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), msg)
if err != nil { if err != nil {
return msg return msg
} }
@ -480,12 +479,13 @@ func (v *validator) getDenyMessage(deny bool) string {
} }
func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta, err error) { func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta, err error) {
kind := v.policyContext.newResource.GetKind() newResource := v.policyContext.NewResource()
kind := newResource.GetKind()
if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" { if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" {
var deployment appsv1.Deployment var deployment appsv1.Deployment
resourceBytes, err := v.policyContext.newResource.MarshalJSON() resourceBytes, err := newResource.MarshalJSON()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -499,7 +499,7 @@ func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta
} else if kind == "CronJob" { } else if kind == "CronJob" {
var cronJob batchv1.CronJob var cronJob batchv1.CronJob
resourceBytes, err := v.policyContext.newResource.MarshalJSON() resourceBytes, err := newResource.MarshalJSON()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -512,7 +512,7 @@ func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta
} else if kind == "Pod" { } else if kind == "Pod" {
var pod corev1.Pod var pod corev1.Pod
resourceBytes, err := v.policyContext.newResource.MarshalJSON() resourceBytes, err := newResource.MarshalJSON()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -566,22 +566,22 @@ func (v *validator) validatePodSecurity() *engineapi.RuleResponse {
} }
func (v *validator) validateResourceWithRule() *engineapi.RuleResponse { func (v *validator) validateResourceWithRule() *engineapi.RuleResponse {
if !isEmptyUnstructured(&v.policyContext.element) { element := v.policyContext.Element()
return v.validatePatterns(v.policyContext.element) if !isEmptyUnstructured(&element) {
return v.validatePatterns(element)
} }
if isDeleteRequest(v.policyContext) { if isDeleteRequest(v.policyContext) {
v.log.V(3).Info("skipping validation on deleted resource") v.log.V(3).Info("skipping validation on deleted resource")
return nil return nil
} }
resp := v.validatePatterns(v.policyContext.NewResource())
resp := v.validatePatterns(v.policyContext.newResource)
return resp return resp
} }
func isDeleteRequest(ctx *PolicyContext) bool { func isDeleteRequest(ctx engineapi.PolicyContext) bool {
newResource := ctx.NewResource()
// if the OldResource is not empty, and the NewResource is empty, the request is a DELETE // if the OldResource is not empty, and the NewResource is empty, the request is a DELETE
return isEmptyUnstructured(&ctx.newResource) return isEmptyUnstructured(&newResource)
} }
func isEmptyUnstructured(u *unstructured.Unstructured) bool { func isEmptyUnstructured(u *unstructured.Unstructured) bool {
@ -597,14 +597,14 @@ func isEmptyUnstructured(u *unstructured.Unstructured) bool {
} }
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule // 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 *PolicyContext, subresourceGVKToAPIResource map[string]*metav1.APIResource) bool { func matches(logger logr.Logger, rule *kyvernov1.Rule, ctx engineapi.PolicyContext, subresourceGVKToAPIResource map[string]*metav1.APIResource) bool {
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.newResource, *rule, ctx.admissionInfo, ctx.excludeGroupRole, ctx.namespaceLabels, "", ctx.subresource) err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.NewResource(), *rule, ctx.AdmissionInfo(), ctx.ExcludeGroupRole(), ctx.NamespaceLabels(), "", ctx.SubResource())
if err == nil { if err == nil {
return true return true
} }
if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) { if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) {
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.oldResource, *rule, ctx.admissionInfo, ctx.excludeGroupRole, ctx.namespaceLabels, "", ctx.subresource) err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.OldResource(), *rule, ctx.AdmissionInfo(), ctx.ExcludeGroupRole(), ctx.NamespaceLabels(), "", ctx.SubResource())
if err == nil { if err == nil {
return true return true
} }
@ -728,7 +728,7 @@ func (v *validator) buildErrorMessage(err error, path string) string {
return fmt.Sprintf("validation error: rule %s execution error: %s", v.rule.Name, err.Error()) return fmt.Sprintf("validation error: rule %s execution error: %s", v.rule.Name, err.Error())
} }
msgRaw, sErr := variables.SubstituteAll(v.log, v.policyContext.jsonContext, v.rule.Validation.Message) msgRaw, sErr := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), v.rule.Validation.Message)
if sErr != nil { if sErr != nil {
v.log.V(2).Info("failed to substitute variables in message", "error", sErr) v.log.V(2).Info("failed to substitute variables in message", "error", sErr)
return fmt.Sprintf("validation error: variables substitution error in rule %s execution error: %s", v.rule.Name, err.Error()) return fmt.Sprintf("validation error: variables substitution error in rule %s execution error: %s", v.rule.Name, err.Error())
@ -759,7 +759,7 @@ func buildAnyPatternErrorMessage(rule *kyvernov1.Rule, errors []string) string {
func (v *validator) substitutePatterns() error { func (v *validator) substitutePatterns() error {
if v.pattern != nil { if v.pattern != nil {
i, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, v.pattern) i, err := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), v.pattern)
if err != nil { if err != nil {
return err return err
} }
@ -769,7 +769,7 @@ func (v *validator) substitutePatterns() error {
} }
if v.anyPattern != nil { if v.anyPattern != nil {
i, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, v.anyPattern) i, err := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), v.anyPattern)
if err != nil { if err != nil {
return err return err
} }
@ -785,19 +785,17 @@ func (v *validator) substituteDeny() error {
if v.deny == nil { if v.deny == nil {
return nil return nil
} }
i, err := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), v.deny)
i, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, v.deny)
if err != nil { if err != nil {
return err return err
} }
v.deny = i.(*kyvernov1.Deny) v.deny = i.(*kyvernov1.Deny)
return nil return nil
} }
// matchesException checks if an exception applies to the resource being admitted // matchesException checks if an exception applies to the resource being admitted
func matchesException( func matchesException(
policyContext *PolicyContext, policyContext engineapi.PolicyContext,
rule *kyvernov1.Rule, rule *kyvernov1.Rule,
subresourceGVKToAPIResource map[string]*metav1.APIResource, subresourceGVKToAPIResource map[string]*metav1.APIResource,
) (*kyvernov2alpha1.PolicyException, error) { ) (*kyvernov2alpha1.PolicyException, error) {
@ -807,13 +805,13 @@ func matchesException(
} }
for _, candidate := range candidates { for _, candidate := range candidates {
err := matched.CheckMatchesResources( err := matched.CheckMatchesResources(
policyContext.newResource, policyContext.NewResource(),
candidate.Spec.Match, candidate.Spec.Match,
policyContext.namespaceLabels, policyContext.NamespaceLabels(),
subresourceGVKToAPIResource, subresourceGVKToAPIResource,
policyContext.subresource, policyContext.SubResource(),
policyContext.admissionInfo, policyContext.AdmissionInfo(),
policyContext.excludeGroupRole, policyContext.ExcludeGroupRole(),
) )
// if there's no error it means a match // if there's no error it means a match
if err == nil { if err == nil {
@ -825,7 +823,7 @@ func matchesException(
// hasPolicyExceptions returns nil when there are no matching exceptions. // hasPolicyExceptions returns nil when there are no matching exceptions.
// A rule response is returned when an exception is matched, or there is an error. // A rule response is returned when an exception is matched, or there is an error.
func hasPolicyExceptions(ctx *PolicyContext, rule *kyvernov1.Rule, subresourceGVKToAPIResource map[string]*metav1.APIResource, log logr.Logger) *engineapi.RuleResponse { func hasPolicyExceptions(ctx engineapi.PolicyContext, rule *kyvernov1.Rule, subresourceGVKToAPIResource map[string]*metav1.APIResource, log logr.Logger) *engineapi.RuleResponse {
// if matches, check if there is a corresponding policy exception // if matches, check if there is a corresponding policy exception
exception, err := matchesException(ctx, rule, subresourceGVKToAPIResource) exception, err := matchesException(ctx, rule, subresourceGVKToAPIResource)
// if we found an exception // if we found an exception