1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-01-20 18:52:16 +00:00

refactor: introduce pss validation handler (#6724)

* refactor: remove rules pointer

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

* refactor: introduce pss validation handler

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

* handler

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

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-03-30 11:51:16 +02:00 committed by GitHub
parent e2a8d9fa04
commit d0841e4918
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 178 additions and 178 deletions

View file

@ -109,31 +109,24 @@ func (r *Rule) HasVerifyImages() bool {
return false return false
} }
// HasYAMLSignatureVerify checks for validate.manifests rule // HasVerifyImageChecks checks whether the verifyImages rule has validation checks
func (r Rule) HasYAMLSignatureVerify() bool { func (r *Rule) HasVerifyImageChecks() bool {
for _, verifyImage := range r.VerifyImages {
if verifyImage.VerifyDigest || verifyImage.Required {
return true
}
}
return false
}
// HasVerifyManifests checks for validate.manifests rule
func (r Rule) HasVerifyManifests() bool {
return r.Validation.Manifests != nil && len(r.Validation.Manifests.Attestors) != 0 return r.Validation.Manifests != nil && len(r.Validation.Manifests.Attestors) != 0
} }
// HasImagesValidationChecks checks whether the verifyImages rule has validation checks // HasValidatePodSecurity checks for validate.podSecurity rule
func (r *Rule) HasImagesValidationChecks() bool { func (r Rule) HasValidatePodSecurity() bool {
for _, v := range r.VerifyImages { return r.Validation.PodSecurity != nil && !datautils.DeepEqual(r.Validation.PodSecurity, &PodSecurity{})
if v.VerifyDigest || v.Required {
return true
}
}
return false
}
// HasYAMLSignatureVerify checks for validate rule
func (p *ClusterPolicy) HasYAMLSignatureVerify() bool {
for _, rule := range p.Spec.Rules {
if rule.HasYAMLSignatureVerify() {
return true
}
}
return false
} }
// HasValidate checks for validate rule // HasValidate checks for validate rule

View file

@ -133,7 +133,6 @@ func (s *Spec) HasMutate() bool {
return true return true
} }
} }
return false return false
} }
@ -144,7 +143,6 @@ func (s *Spec) HasValidate() bool {
return true return true
} }
} }
return false return false
} }
@ -155,18 +153,16 @@ func (s *Spec) HasGenerate() bool {
return true return true
} }
} }
return false return false
} }
// HasImagesValidationChecks checks for image verification rules invoked during resource validation // HasVerifyImageChecks checks for image verification rules invoked during resource validation
func (s *Spec) HasImagesValidationChecks() bool { func (s *Spec) HasVerifyImageChecks() bool {
for _, rule := range s.Rules { for _, rule := range s.Rules {
if rule.HasImagesValidationChecks() { if rule.HasVerifyImageChecks() {
return true return true
} }
} }
return false return false
} }
@ -177,18 +173,16 @@ func (s *Spec) HasVerifyImages() bool {
return true return true
} }
} }
return false return false
} }
// HasYAMLSignatureVerify checks for image verification rules invoked during resource mutation // HasVerifyManifests checks for image verification rules invoked during resource mutation
func (s *Spec) HasYAMLSignatureVerify() bool { func (s *Spec) HasVerifyManifests() bool {
for _, rule := range s.Rules { for _, rule := range s.Rules {
if rule.HasYAMLSignatureVerify() { if rule.HasVerifyManifests() {
return true return true
} }
} }
return false return false
} }
@ -197,7 +191,6 @@ func (s *Spec) BackgroundProcessingEnabled() bool {
if s.Background == nil { if s.Background == nil {
return true return true
} }
return *s.Background return *s.Background
} }

View file

@ -77,31 +77,24 @@ func (r *Rule) HasVerifyImages() bool {
return false return false
} }
// HasYAMLSignatureVerify checks for validate.manifests rule // HasVerifyImageChecks checks whether the verifyImages rule has validation checks
func (r Rule) HasYAMLSignatureVerify() bool { func (r *Rule) HasVerifyImageChecks() bool {
return r.Validation.Manifests != nil && len(r.Validation.Manifests.Attestors) != 0
}
// HasImagesValidationChecks checks whether the verifyImages rule has validation checks
func (r *Rule) HasImagesValidationChecks() bool {
for _, v := range r.VerifyImages { for _, v := range r.VerifyImages {
if v.VerifyDigest || v.Required { if v.VerifyDigest || v.Required {
return true return true
} }
} }
return false return false
} }
// HasYAMLSignatureVerify checks for validate rule // HasVerifyManifests checks for validate.manifests rule
func (p *ClusterPolicy) HasYAMLSignatureVerify() bool { func (r Rule) HasVerifyManifests() bool {
for _, rule := range p.Spec.Rules { return r.Validation.Manifests != nil && len(r.Validation.Manifests.Attestors) != 0
if rule.HasYAMLSignatureVerify() { }
return true
}
}
return false // HasValidatePodSecurity checks for validate.podSecurity rule
func (r Rule) HasValidatePodSecurity() bool {
return r.Validation.PodSecurity != nil && !datautils.DeepEqual(r.Validation.PodSecurity, &kyvernov1.PodSecurity{})
} }
// HasValidate checks for validate rule // HasValidate checks for validate rule

View file

@ -121,10 +121,10 @@ func (s *Spec) HasGenerate() bool {
return false return false
} }
// HasImagesValidationChecks checks for image verification rules invoked during resource validation // HasVerifyImageChecks checks for image verification rules invoked during resource validation
func (s *Spec) HasImagesValidationChecks() bool { func (s *Spec) HasVerifyImageChecks() bool {
for _, rule := range s.Rules { for _, rule := range s.Rules {
if rule.HasImagesValidationChecks() { if rule.HasVerifyImageChecks() {
return true return true
} }
} }
@ -143,10 +143,10 @@ func (s *Spec) HasVerifyImages() bool {
return false return false
} }
// HasYAMLSignatureVerify checks for image verification rules invoked during resource mutation // HasVerifyManifests checks for image verification rules invoked during resource mutation
func (s *Spec) HasYAMLSignatureVerify() bool { func (s *Spec) HasVerifyManifests() bool {
for _, rule := range s.Rules { for _, rule := range s.Rules {
if rule.HasYAMLSignatureVerify() { if rule.HasVerifyManifests() {
return true return true
} }
} }

View file

@ -502,7 +502,7 @@ OuterLoop:
var policyHasValidate bool var policyHasValidate bool
for _, rule := range autogen.ComputeRules(c.Policy) { for _, rule := range autogen.ComputeRules(c.Policy) {
if rule.HasValidate() || rule.HasImagesValidationChecks() { if rule.HasValidate() || rule.HasVerifyImageChecks() {
policyHasValidate = true policyHasValidate = true
} }
} }
@ -702,7 +702,7 @@ func ProcessValidateEngineResponse(policy kyvernov1.PolicyInterface, validateRes
printCount := 0 printCount := 0
for _, policyRule := range autogen.ComputeRules(policy) { for _, policyRule := range autogen.ComputeRules(policy) {
ruleFoundInEngineResponse := false ruleFoundInEngineResponse := false
if !policyRule.HasValidate() && !policyRule.HasImagesValidationChecks() && !policyRule.HasVerifyImages() { if !policyRule.HasValidate() && !policyRule.HasVerifyImageChecks() && !policyRule.HasVerifyImages() {
continue continue
} }

View file

@ -47,7 +47,7 @@ func RemoveNonValidationPolicies(policies ...kyvernov1.PolicyInterface) []kyvern
var validationPolicies []kyvernov1.PolicyInterface var validationPolicies []kyvernov1.PolicyInterface
for _, pol := range policies { for _, pol := range policies {
spec := pol.GetSpec() spec := pol.GetSpec()
if spec.HasVerifyImages() || spec.HasValidate() || spec.HasYAMLSignatureVerify() { if spec.HasVerifyImages() || spec.HasValidate() || spec.HasVerifyManifests() {
validationPolicies = append(validationPolicies, pol) validationPolicies = append(validationPolicies, pol)
} }
} }

View file

@ -735,7 +735,7 @@ func (c *controller) buildResourceValidatingWebhookConfiguration(cfg config.Conf
c.recordPolicyState(config.ValidatingWebhookConfigurationName, policies...) c.recordPolicyState(config.ValidatingWebhookConfigurationName, policies...)
for _, p := range policies { for _, p := range policies {
spec := p.GetSpec() spec := p.GetSpec()
if spec.HasValidate() || spec.HasGenerate() || spec.HasMutate() || spec.HasImagesValidationChecks() || spec.HasYAMLSignatureVerify() { if spec.HasValidate() || spec.HasGenerate() || spec.HasMutate() || spec.HasVerifyImageChecks() || spec.HasVerifyManifests() {
if spec.GetFailurePolicy() == kyvernov1.Ignore { if spec.GetFailurePolicy() == kyvernov1.Ignore {
c.mergeWebhook(ignore, p, true) c.mergeWebhook(ignore, p, true)
} else { } else {
@ -826,10 +826,10 @@ func (c *controller) mergeWebhook(dst *webhook, policy kyvernov1.PolicyInterface
matchedGVK = append(matchedGVK, rule.Generation.CloneList.Kinds...) matchedGVK = append(matchedGVK, rule.Generation.CloneList.Kinds...)
continue continue
} }
if (updateValidate && rule.HasValidate() || rule.HasImagesValidationChecks()) || if (updateValidate && rule.HasValidate() || rule.HasVerifyImageChecks()) ||
(updateValidate && rule.HasMutate() && rule.IsMutateExisting()) || (updateValidate && rule.HasMutate() && rule.IsMutateExisting()) ||
(!updateValidate && rule.HasMutate()) && !rule.IsMutateExisting() || (!updateValidate && rule.HasMutate()) && !rule.IsMutateExisting() ||
(!updateValidate && rule.HasVerifyImages()) || (!updateValidate && rule.HasYAMLSignatureVerify()) { (!updateValidate && rule.HasVerifyImages()) || (!updateValidate && rule.HasVerifyManifests()) {
matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...) matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...)
} }
} }

View file

@ -30,11 +30,12 @@ type engine struct {
rclient registryclient.Client rclient registryclient.Client
engineContextLoaderFactory engineapi.EngineContextLoaderFactory engineContextLoaderFactory engineapi.EngineContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector exceptionSelector engineapi.PolicyExceptionSelector
validateManifestHandler handlers.Handler
mutateResourceHandler handlers.Handler
mutateExistingHandler handlers.Handler
validateResourceHandler handlers.Handler validateResourceHandler handlers.Handler
validateImageHandler handlers.Handler validateImageHandler handlers.Handler
validateManifestHandler handlers.Handler
validatePssHandler handlers.Handler
mutateResourceHandler handlers.Handler
mutateExistingHandler handlers.Handler
} }
func NewEngine( func NewEngine(
@ -62,9 +63,10 @@ func NewEngine(
rclient: rclient, rclient: rclient,
engineContextLoaderFactory: engineContextLoaderFactory, engineContextLoaderFactory: engineContextLoaderFactory,
exceptionSelector: exceptionSelector, exceptionSelector: exceptionSelector,
validateManifestHandler: validation.NewValidateManifestHandler(client),
validateImageHandler: validation.NewValidateImageHandler(configuration),
validateResourceHandler: validation.NewValidateResourceHandler(engineContextLoaderFactory), validateResourceHandler: validation.NewValidateResourceHandler(engineContextLoaderFactory),
validateImageHandler: validation.NewValidateImageHandler(configuration),
validateManifestHandler: validation.NewValidateManifestHandler(client),
validatePssHandler: validation.NewValidatePssHandler(),
mutateResourceHandler: mutation.NewMutateResourceHandler(engineContextLoaderFactory), mutateResourceHandler: mutation.NewMutateResourceHandler(engineContextLoaderFactory),
mutateExistingHandler: mutation.NewMutateExistingHandler(client, engineContextLoaderFactory), mutateExistingHandler: mutation.NewMutateExistingHandler(client, engineContextLoaderFactory),
} }

View file

@ -0,0 +1,115 @@
package validation
import (
"context"
"encoding/json"
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/pss"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type validatePssHandler struct{}
func NewValidatePssHandler() handlers.Handler {
return validatePssHandler{}
}
func (h validatePssHandler) Process(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
podSecurity := rule.Validation.PodSecurity
// Marshal pod metadata and spec
podSpec, metadata, err := getSpec(resource)
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "Error while getting new resource", err))
}
pod := &corev1.Pod{
Spec: *podSpec,
ObjectMeta: *metadata,
}
allowed, pssChecks, err := pss.EvaluatePod(podSecurity, pod)
if err != nil {
return resource, handlers.RuleResponses(internal.RuleError(rule, engineapi.Validation, "failed to parse pod security api version", err))
}
podSecurityChecks := &engineapi.PodSecurityChecks{
Level: podSecurity.Level,
Version: podSecurity.Version,
Checks: pssChecks,
}
if allowed {
msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
rspn := internal.RulePass(rule, engineapi.Validation, msg)
rspn.PodSecurityChecks = podSecurityChecks
return resource, handlers.RuleResponses(rspn)
} else {
msg := fmt.Sprintf(`Validation rule '%s' failed. It violates PodSecurity "%s:%s": %s`, rule.Name, podSecurity.Level, podSecurity.Version, pss.FormatChecksPrint(pssChecks))
rspn := internal.RuleResponse(rule, engineapi.Validation, msg, engineapi.RuleStatusFail)
rspn.PodSecurityChecks = podSecurityChecks
return resource, handlers.RuleResponses(rspn)
}
}
func getSpec(resource unstructured.Unstructured) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta, err error) {
kind := resource.GetKind()
if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" {
var deployment appsv1.Deployment
resourceBytes, err := resource.MarshalJSON()
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(resourceBytes, &deployment)
if err != nil {
return nil, nil, err
}
podSpec = &deployment.Spec.Template.Spec
metadata = &deployment.Spec.Template.ObjectMeta
return podSpec, metadata, nil
} else if kind == "CronJob" {
var cronJob batchv1.CronJob
resourceBytes, err := resource.MarshalJSON()
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(resourceBytes, &cronJob)
if err != nil {
return nil, nil, err
}
podSpec = &cronJob.Spec.JobTemplate.Spec.Template.Spec
metadata = &cronJob.Spec.JobTemplate.ObjectMeta
} else if kind == "Pod" {
var pod corev1.Pod
resourceBytes, err := resource.MarshalJSON()
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(resourceBytes, &pod)
if err != nil {
return nil, nil, err
}
podSpec = &pod.Spec
metadata = &pod.ObjectMeta
return podSpec, metadata, nil
}
if err != nil {
return nil, nil, err
}
return podSpec, metadata, err
}

View file

@ -15,14 +15,9 @@ import (
engineutils "github.com/kyverno/kyverno/pkg/engine/utils" engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/validate" "github.com/kyverno/kyverno/pkg/engine/validate"
"github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/pss"
"github.com/kyverno/kyverno/pkg/utils/api" "github.com/kyverno/kyverno/pkg/utils/api"
datautils "github.com/kyverno/kyverno/pkg/utils/data" datautils "github.com/kyverno/kyverno/pkg/utils/data"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
) )
@ -60,7 +55,6 @@ type validator struct {
pattern apiextensions.JSON pattern apiextensions.JSON
anyPattern apiextensions.JSON anyPattern apiextensions.JSON
deny *kyvernov1.Deny deny *kyvernov1.Deny
podSecurity *kyvernov1.PodSecurity
forEach []kyvernov1.ForEachValidation forEach []kyvernov1.ForEachValidation
contextLoader engineapi.EngineContextLoader contextLoader engineapi.EngineContextLoader
nesting int nesting int
@ -77,7 +71,6 @@ func newValidator(log logr.Logger, contextLoader engineapi.EngineContextLoader,
pattern: rule.Validation.GetPattern(), pattern: rule.Validation.GetPattern(),
anyPattern: rule.Validation.GetAnyPattern(), anyPattern: rule.Validation.GetAnyPattern(),
deny: rule.Validation.Deny, deny: rule.Validation.Deny,
podSecurity: rule.Validation.PodSecurity,
forEach: rule.Validation.ForEachValidation, forEach: rule.Validation.ForEachValidation,
} }
} }
@ -142,13 +135,6 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
return ruleResponse return ruleResponse
} }
if v.podSecurity != nil {
if !engineutils.IsDeleteRequest(v.policyContext) {
ruleResponse := v.validatePodSecurity()
return ruleResponse
}
}
if v.forEach != nil { if v.forEach != nil {
ruleResponse := v.validateForEach(ctx) ruleResponse := v.validateForEach(ctx)
return ruleResponse return ruleResponse
@ -274,93 +260,6 @@ func (v *validator) getDenyMessage(deny bool) string {
} }
} }
func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta, err error) {
newResource := v.policyContext.NewResource()
kind := newResource.GetKind()
if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" {
var deployment appsv1.Deployment
resourceBytes, err := newResource.MarshalJSON()
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(resourceBytes, &deployment)
if err != nil {
return nil, nil, err
}
podSpec = &deployment.Spec.Template.Spec
metadata = &deployment.Spec.Template.ObjectMeta
return podSpec, metadata, nil
} else if kind == "CronJob" {
var cronJob batchv1.CronJob
resourceBytes, err := newResource.MarshalJSON()
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(resourceBytes, &cronJob)
if err != nil {
return nil, nil, err
}
podSpec = &cronJob.Spec.JobTemplate.Spec.Template.Spec
metadata = &cronJob.Spec.JobTemplate.ObjectMeta
} else if kind == "Pod" {
var pod corev1.Pod
resourceBytes, err := newResource.MarshalJSON()
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(resourceBytes, &pod)
if err != nil {
return nil, nil, err
}
podSpec = &pod.Spec
metadata = &pod.ObjectMeta
return podSpec, metadata, nil
}
if err != nil {
return nil, nil, err
}
return podSpec, metadata, err
}
// Unstructured
func (v *validator) validatePodSecurity() *engineapi.RuleResponse {
// Marshal pod metadata and spec
podSpec, metadata, err := getSpec(v)
if err != nil {
return internal.RuleError(v.rule, engineapi.Validation, "Error while getting new resource", err)
}
pod := &corev1.Pod{
Spec: *podSpec,
ObjectMeta: *metadata,
}
allowed, pssChecks, err := pss.EvaluatePod(v.podSecurity, pod)
if err != nil {
return internal.RuleError(v.rule, engineapi.Validation, "failed to parse pod security api version", err)
}
podSecurityChecks := &engineapi.PodSecurityChecks{
Level: v.podSecurity.Level,
Version: v.podSecurity.Version,
Checks: pssChecks,
}
if allowed {
msg := fmt.Sprintf("Validation rule '%s' passed.", v.rule.Name)
rspn := internal.RulePass(v.rule, engineapi.Validation, msg)
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 := internal.RuleResponse(v.rule, engineapi.Validation, msg, engineapi.RuleStatusFail)
rspn.PodSecurityChecks = podSecurityChecks
return rspn
}
}
func (v *validator) validateResourceWithRule() *engineapi.RuleResponse { func (v *validator) validateResourceWithRule() *engineapi.RuleResponse {
element := v.policyContext.Element() element := v.policyContext.Element()
if !engineutils.IsEmptyUnstructured(&element) { if !engineutils.IsEmptyUnstructured(&element) {

View file

@ -42,18 +42,23 @@ func (e *engine) validateResource(
logger := internal.LoggerWithRule(logger, rule) logger := internal.LoggerWithRule(logger, rule)
startTime := time.Now() startTime := time.Now()
hasValidate := rule.HasValidate() hasValidate := rule.HasValidate()
hasValidateImage := rule.HasImagesValidationChecks() hasVerifyImageChecks := rule.HasVerifyImageChecks()
hasYAMLSignatureVerify := rule.HasYAMLSignatureVerify() if !hasValidate && !hasVerifyImageChecks {
if !hasValidate && !hasValidateImage {
continue continue
} }
var handler handlers.Handler var handler handlers.Handler
if hasValidate && !hasYAMLSignatureVerify { if hasValidate {
handler = e.validateResourceHandler hasVerifyManifest := rule.HasVerifyManifests()
} else if hasValidateImage { hasValidatePss := rule.HasValidatePodSecurity()
if hasVerifyManifest {
handler = e.validateManifestHandler
} else if hasValidatePss {
handler = e.validatePssHandler
} else {
handler = e.validateResourceHandler
}
} else if hasVerifyImageChecks {
handler = e.validateImageHandler handler = e.validateImageHandler
} else if hasYAMLSignatureVerify {
handler = e.validateManifestHandler
} }
if handler != nil { if handler != nil {
_, ruleResp := e.invokeRuleHandler(ctx, logger, handler, policyContext, policyContext.NewResource(), rule, engineapi.Validation) _, ruleResp := e.invokeRuleHandler(ctx, logger, handler, policyContext, policyContext.NewResource(), rule, engineapi.Validation)

View file

@ -140,7 +140,7 @@ func (m *policyMap) set(key string, policy kyvernov1.PolicyInterface, client Res
hasValidate := rule.HasValidate() hasValidate := rule.HasValidate()
hasGenerate := rule.HasGenerate() hasGenerate := rule.HasGenerate()
hasVerifyImages := rule.HasVerifyImages() hasVerifyImages := rule.HasVerifyImages()
hasImagesValidationChecks := rule.HasImagesValidationChecks() hasImagesValidationChecks := rule.HasVerifyImageChecks()
for gvrs := range entries { for gvrs := range entries {
entry := kindStates[gvrs] entry := kindStates[gvrs]
entry.hasMutate = entry.hasMutate || hasMutate entry.hasMutate = entry.hasMutate || hasMutate