From 9ed14cb779d01ccbbf7a021932993da0ec2ed3ef Mon Sep 17 00:00:00 2001 From: Mariam Fahmy Date: Mon, 29 Jan 2024 13:49:17 +0200 Subject: [PATCH] feat: support vap bindings in reports (#9506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: support vap bindings in reports Signed-off-by: Mariam Fahmy * fix: add binding to the rule response Signed-off-by: Mariam Fahmy * add chainsaw test Signed-off-by: Mariam Fahmy * fix lint Signed-off-by: Mariam Fahmy * fix chainsaw Signed-off-by: Mariam Fahmy * add chainsaw tests Signed-off-by: Mariam Fahmy * fix chainsaw tests Signed-off-by: Mariam Fahmy --------- Signed-off-by: Mariam Fahmy Co-authored-by: Vishal Choudhary Co-authored-by: Charles-Edouard Brétéché --- cmd/reports-controller/main.go | 3 + .../report/background/controller.go | 92 +++++++++++++++---- pkg/controllers/report/utils/scanner.go | 10 +- pkg/controllers/report/utils/utils.go | 12 +++ pkg/engine/api/ruleresponse.go | 12 +++ pkg/utils/report/labels.go | 29 ++++-- pkg/utils/report/results.go | 5 + .../validatingadmissionpolicy.go | 37 +++++--- .../README.md | 11 +++ .../chainsaw-test.yaml | 27 ++++++ .../deployment-assert.yaml | 5 + .../deployment.yaml | 22 +++++ .../ns.yaml | 6 ++ .../policy.yaml | 30 ++++++ .../report-assert.yaml | 26 ++++++ .../README.md | 11 +++ .../chainsaw-test.yaml | 21 +++++ .../deployment-assert.yaml | 11 +++ .../deployment.yaml | 45 +++++++++ .../policy.yaml | 30 ++++++ .../report-error.yaml | 52 +++++++++++ .../README.md | 11 +++ .../chainsaw-test.yaml | 27 ++++++ .../deployment-assert.yaml | 5 + .../deployment.yaml | 22 +++++ .../ns.yaml | 6 ++ .../policy.yaml | 30 ++++++ .../report-assert.yaml | 25 +++++ 28 files changed, 585 insertions(+), 38 deletions(-) create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/README.md create mode 100755 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/chainsaw-test.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/deployment-assert.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/deployment.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/ns.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/policy.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/report-assert.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/README.md create mode 100755 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/chainsaw-test.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/deployment-assert.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/deployment.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/policy.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/report-error.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/README.md create mode 100755 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/chainsaw-test.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/deployment-assert.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/deployment.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/ns.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/policy.yaml create mode 100644 test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/report-assert.yaml diff --git a/cmd/reports-controller/main.go b/cmd/reports-controller/main.go index 768661d7dd..9d60a6b4c5 100644 --- a/cmd/reports-controller/main.go +++ b/cmd/reports-controller/main.go @@ -59,9 +59,11 @@ func createReportControllers( var ctrls []internal.Controller var warmups []func(context.Context) error var vapInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyInformer + var vapBindingInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyBindingInformer // check if validating admission policies are registered in the API server if validatingAdmissionPolicyReports { vapInformer = kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies() + vapBindingInformer = kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicyBindings() } kyvernoV1 := kyvernoInformer.Kyverno().V1() @@ -120,6 +122,7 @@ func createReportControllers( kyvernoV1.ClusterPolicies(), kyvernoV2beta1.PolicyExceptions(), vapInformer, + vapBindingInformer, kubeInformer.Core().V1().Namespaces(), resourceReportController, backgroundScanInterval, diff --git a/pkg/controllers/report/background/controller.go b/pkg/controllers/report/background/controller.go index eb51900ebd..328bd5b725 100644 --- a/pkg/controllers/report/background/controller.go +++ b/pkg/controllers/report/background/controller.go @@ -57,13 +57,14 @@ type controller struct { engine engineapi.Engine // listers - polLister kyvernov1listers.PolicyLister - cpolLister kyvernov1listers.ClusterPolicyLister - polexLister kyvernov2beta1listers.PolicyExceptionLister - vapLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyLister - bgscanrLister cache.GenericLister - cbgscanrLister cache.GenericLister - nsLister corev1listers.NamespaceLister + polLister kyvernov1listers.PolicyLister + cpolLister kyvernov1listers.ClusterPolicyLister + polexLister kyvernov2beta1listers.PolicyExceptionLister + vapLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyLister + vapBindingLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyBindingLister + bgscanrLister cache.GenericLister + cbgscanrLister cache.GenericLister + nsLister corev1listers.NamespaceLister // queue queue workqueue.RateLimitingInterface @@ -89,6 +90,7 @@ func NewController( cpolInformer kyvernov1informers.ClusterPolicyInformer, polexInformer kyvernov2beta1informers.PolicyExceptionInformer, vapInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyInformer, + vapBindingInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyBindingInformer, nsInformer corev1informers.NamespaceInformer, metadataCache resource.MetadataCache, forceDelay time.Duration, @@ -125,6 +127,12 @@ func NewController( logger.Error(err, "failed to register event handlers") } } + if vapBindingInformer != nil { + c.vapBindingLister = vapBindingInformer.Lister() + if _, err := controllerutils.AddEventHandlersT(vapBindingInformer.Informer(), c.addVAPBinding, c.updateVAPBinding, c.deleteVAPBinding); err != nil { + logger.Error(err, "failed to register event handlers") + } + } if _, err := controllerutils.AddEventHandlersT(polInformer.Informer(), c.addPolicy, c.updatePolicy, c.deletePolicy); err != nil { logger.Error(err, "failed to register event handlers") } @@ -195,6 +203,20 @@ func (c *controller) deleteVAP(obj *admissionregistrationv1alpha1.ValidatingAdmi c.enqueueResources() } +func (c *controller) addVAPBinding(obj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding) { + c.enqueueResources() +} + +func (c *controller) updateVAPBinding(old, obj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding) { + if old.GetResourceVersion() != obj.GetResourceVersion() { + c.enqueueResources() + } +} + +func (c *controller) deleteVAPBinding(obj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding) { + c.enqueueResources() +} + func (c *controller) enqueueResources() { for _, key := range c.metadataCache.GetAllResourceKeys() { c.queue.Add(key) @@ -225,7 +247,7 @@ func (c *controller) getMeta(namespace, name string) (metav1.Object, error) { } } -func (c *controller) needsReconcile(namespace, name, hash string, exceptions []kyvernov2beta1.PolicyException, policies ...engineapi.GenericPolicy) (bool, bool, error) { +func (c *controller) needsReconcile(namespace, name, hash string, exceptions []kyvernov2beta1.PolicyException, bindings []admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, policies ...engineapi.GenericPolicy) (bool, bool, error) { // if the reportMetadata does not exist, we need a full reconcile reportMetadata, err := c.getMeta(namespace, name) if err != nil { @@ -260,6 +282,9 @@ func (c *controller) needsReconcile(namespace, name, hash string, exceptions []k for _, exception := range exceptions { expected[reportutils.PolicyExceptionLabel(exception)] = exception.GetResourceVersion() } + for _, binding := range bindings { + expected[reportutils.ValidatingAdmissionPolicyBindingLabel(binding)] = binding.GetResourceVersion() + } actual := map[string]string{} for key, value := range reportMetadata.GetLabels() { if reportutils.IsPolicyLabel(key) { @@ -282,6 +307,7 @@ func (c *controller) reconcileReport( gvk schema.GroupVersionKind, resource resource.Resource, exceptions []kyvernov2beta1.PolicyException, + bindings []admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, policies ...engineapi.GenericPolicy, ) error { // namespace labels to be used by the scanner @@ -314,6 +340,9 @@ func (c *controller) reconcileReport( for _, exception := range exceptions { expected[reportutils.PolicyExceptionLabel(exception)] = exception.GetResourceVersion() } + for _, binding := range bindings { + expected[reportutils.ValidatingAdmissionPolicyBindingLabel(binding)] = binding.GetResourceVersion() + } actual := map[string]string{} for key, value := range observed.GetLabels() { @@ -346,11 +375,22 @@ func (c *controller) reconcileReport( policyNameToLabel[key] = reportutils.PolicyExceptionLabel(exception) } + for _, binding := range bindings { + key, err := cache.MetaNamespaceKeyFunc(binding) + if err != nil { + return err + } + policyNameToLabel[key] = reportutils.ValidatingAdmissionPolicyBindingLabel(binding) + } + for _, result := range observed.GetResults() { // if the policy did not change, keep the result label := policyNameToLabel[result.Policy] exceptionLabel := policyNameToLabel[result.Properties["exception"]] - if (label != "" && expected[label] == actual[label]) || (exceptionLabel != "" && expected[exceptionLabel] == actual[exceptionLabel]) { + vapBindingLabel := policyNameToLabel[result.Properties["binding"]] + if (label != "" && expected[label] == actual[label]) || + (exceptionLabel != "" && expected[exceptionLabel] == actual[exceptionLabel]) || + (vapBindingLabel != "" && expected[vapBindingLabel] == actual[vapBindingLabel]) { ruleResults = append(ruleResults, result) } } @@ -358,15 +398,24 @@ func (c *controller) reconcileReport( // calculate necessary results for _, policy := range policies { reevaluate := false - for _, polex := range exceptions { - if actual[reportutils.PolicyExceptionLabel(polex)] != polex.GetResourceVersion() { - reevaluate = true - break + if policy.GetType() == engineapi.KyvernoPolicyType { + for _, polex := range exceptions { + if actual[reportutils.PolicyExceptionLabel(polex)] != polex.GetResourceVersion() { + reevaluate = true + break + } + } + } else { + for _, binding := range bindings { + if actual[reportutils.ValidatingAdmissionPolicyBindingLabel(binding)] != binding.GetResourceVersion() { + reevaluate = true + break + } } } if full || reevaluate || actual[reportutils.PolicyLabel(policy)] != policy.GetResourceVersion() { scanner := utils.NewScanner(logger, c.engine, c.config, c.jp, c.client) - for _, result := range scanner.ScanResource(ctx, *target, nsLabels, policy) { + for _, result := range scanner.ScanResource(ctx, *target, nsLabels, bindings, policy) { if result.Error != nil { return result.Error } else if result.EngineResponse != nil { @@ -388,6 +437,9 @@ func (c *controller) reconcileReport( for _, exception := range exceptions { reportutils.SetPolicyExceptionLabel(desired, exception) } + for _, binding := range bindings { + reportutils.SetValidatingAdmissionPolicyBindingLabel(desired, binding) + } reportutils.SetResourceVersionLabels(desired, target) reportutils.SetResults(desired, ruleResults...) if full || !controllerutils.HasAnnotation(desired, annotationLastScanTime) { @@ -472,20 +524,28 @@ func (c *controller) reconcile(ctx context.Context, log logr.Logger, key, namesp policies = append(policies, engineapi.NewValidatingAdmissionPolicy(pol)) } } + var vapBindings []admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding + if c.vapBindingLister != nil { + // load validating admission policy bindings + vapBindings, err = utils.FetchValidatingAdmissionPolicyBindings(c.vapBindingLister) + if err != nil { + return err + } + } // load policy exceptions with background process enabled exceptions, err := utils.FetchPolicyExceptions(c.polexLister, namespace) if err != nil { return err } // we have the resource, check if we need to reconcile - if needsReconcile, full, err := c.needsReconcile(namespace, name, resource.Hash, exceptions, policies...); err != nil { + if needsReconcile, full, err := c.needsReconcile(namespace, name, resource.Hash, exceptions, vapBindings, policies...); err != nil { return err } else { defer func() { c.queue.AddAfter(key, c.forceDelay) }() if needsReconcile { - return c.reconcileReport(ctx, namespace, name, full, uid, gvk, resource, exceptions, policies...) + return c.reconcileReport(ctx, namespace, name, full, uid, gvk, resource, exceptions, vapBindings, policies...) } } return nil diff --git a/pkg/controllers/report/utils/scanner.go b/pkg/controllers/report/utils/scanner.go index 12238f838a..9b4da7b96b 100644 --- a/pkg/controllers/report/utils/scanner.go +++ b/pkg/controllers/report/utils/scanner.go @@ -13,6 +13,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/jmespath" "github.com/kyverno/kyverno/pkg/validatingadmissionpolicy" "go.uber.org/multierr" + "k8s.io/api/admissionregistration/v1alpha1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -30,7 +31,7 @@ type ScanResult struct { } type Scanner interface { - ScanResource(context.Context, unstructured.Unstructured, map[string]string, ...engineapi.GenericPolicy) map[*engineapi.GenericPolicy]ScanResult + ScanResource(context.Context, unstructured.Unstructured, map[string]string, []v1alpha1.ValidatingAdmissionPolicyBinding, ...engineapi.GenericPolicy) map[*engineapi.GenericPolicy]ScanResult } func NewScanner( @@ -49,7 +50,7 @@ func NewScanner( } } -func (s *scanner) ScanResource(ctx context.Context, resource unstructured.Unstructured, nsLabels map[string]string, policies ...engineapi.GenericPolicy) map[*engineapi.GenericPolicy]ScanResult { +func (s *scanner) ScanResource(ctx context.Context, resource unstructured.Unstructured, nsLabels map[string]string, bindings []v1alpha1.ValidatingAdmissionPolicyBinding, policies ...engineapi.GenericPolicy) map[*engineapi.GenericPolicy]ScanResult { results := map[*engineapi.GenericPolicy]ScanResult{} for i, policy := range policies { var errors []error @@ -79,6 +80,11 @@ func (s *scanner) ScanResource(ctx context.Context, resource unstructured.Unstru } else { pol := policy.AsValidatingAdmissionPolicy() policyData := validatingadmissionpolicy.NewPolicyData(*pol) + for _, binding := range bindings { + if binding.Spec.PolicyName == pol.Name { + policyData.AddBinding(binding) + } + } res, err := validatingadmissionpolicy.Validate(policyData, resource, s.client) if err != nil { errors = append(errors, err) diff --git a/pkg/controllers/report/utils/utils.go b/pkg/controllers/report/utils/utils.go index f2b302eaf7..109649d1ae 100644 --- a/pkg/controllers/report/utils/utils.go +++ b/pkg/controllers/report/utils/utils.go @@ -136,3 +136,15 @@ func FetchValidatingAdmissionPolicies(vapLister admissionregistrationv1alpha1lis } return policies, nil } + +func FetchValidatingAdmissionPolicyBindings(vapBindingLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyBindingLister) ([]admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, error) { + var bindings []admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding + if pols, err := vapBindingLister.List(labels.Everything()); err != nil { + return nil, err + } else { + for _, pol := range pols { + bindings = append(bindings, *pol) + } + } + return bindings, nil +} diff --git a/pkg/engine/api/ruleresponse.go b/pkg/engine/api/ruleresponse.go index 2c3ccd2ef0..b0726439da 100644 --- a/pkg/engine/api/ruleresponse.go +++ b/pkg/engine/api/ruleresponse.go @@ -5,6 +5,7 @@ import ( kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1" pssutils "github.com/kyverno/kyverno/pkg/pss/utils" + "k8s.io/api/admissionregistration/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/pod-security-admission/api" @@ -44,6 +45,8 @@ type RuleResponse struct { podSecurityChecks *PodSecurityChecks // exception is the exception applied (if any) exception *kyvernov2beta1.PolicyException + // binding is the validatingadmissionpolicybinding (if any) + binding *v1alpha1.ValidatingAdmissionPolicyBinding // emitWarning enable passing rule message as warning to api server warning header emitWarning bool } @@ -90,6 +93,11 @@ func (r RuleResponse) WithException(exception *kyvernov2beta1.PolicyException) * return &r } +func (r RuleResponse) WithBinding(binding *v1alpha1.ValidatingAdmissionPolicyBinding) *RuleResponse { + r.binding = binding + return &r +} + func (r RuleResponse) WithPodSecurityChecks(checks PodSecurityChecks) *RuleResponse { r.podSecurityChecks = &checks return &r @@ -125,6 +133,10 @@ func (r *RuleResponse) Exception() *kyvernov2beta1.PolicyException { return r.exception } +func (r *RuleResponse) ValidatingAdmissionPolicyBinding() *v1alpha1.ValidatingAdmissionPolicyBinding { + return r.binding +} + func (r *RuleResponse) IsException() bool { return r.exception != nil } diff --git a/pkg/utils/report/labels.go b/pkg/utils/report/labels.go index 26ed65e80f..4b18fc6e5a 100644 --- a/pkg/utils/report/labels.go +++ b/pkg/utils/report/labels.go @@ -13,6 +13,8 @@ import ( kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1" engineapi "github.com/kyverno/kyverno/pkg/engine/api" controllerutils "github.com/kyverno/kyverno/pkg/utils/controller" + "k8s.io/api/admissionregistration/v1alpha1" + admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -28,18 +30,23 @@ const ( AnnotationResourceNamespace = "audit.kyverno.io/resource.namespace" AnnotationResourceName = "audit.kyverno.io/resource.name" // policy labels - LabelDomainClusterPolicy = "cpol.kyverno.io" - LabelDomainPolicy = "pol.kyverno.io" - LabelPrefixClusterPolicy = LabelDomainClusterPolicy + "/" - LabelPrefixPolicy = LabelDomainPolicy + "/" - LabelPrefixPolicyException = "polex.kyverno.io/" - LabelPrefixValidatingAdmissionPolicy = "validatingadmissionpolicy.apiserver.io/" + LabelDomainClusterPolicy = "cpol.kyverno.io" + LabelDomainPolicy = "pol.kyverno.io" + LabelPrefixClusterPolicy = LabelDomainClusterPolicy + "/" + LabelPrefixPolicy = LabelDomainPolicy + "/" + LabelPrefixPolicyException = "polex.kyverno.io/" + LabelPrefixValidatingAdmissionPolicy = "validatingadmissionpolicy.apiserver.io/" + LabelPrefixValidatingAdmissionPolicyBinding = "validatingadmissionpolicybinding.apiserver.io/" // aggregated admission report label LabelAggregatedReport = "audit.kyverno.io/report.aggregate" ) func IsPolicyLabel(label string) bool { - return strings.HasPrefix(label, LabelPrefixPolicy) || strings.HasPrefix(label, LabelPrefixClusterPolicy) || strings.HasPrefix(label, LabelPrefixPolicyException) + return strings.HasPrefix(label, LabelPrefixPolicy) || + strings.HasPrefix(label, LabelPrefixClusterPolicy) || + strings.HasPrefix(label, LabelPrefixPolicyException) || + strings.HasPrefix(label, LabelPrefixValidatingAdmissionPolicy) || + strings.HasPrefix(label, LabelPrefixValidatingAdmissionPolicyBinding) } func PolicyNameFromLabel(namespace, label string) (string, error) { @@ -79,6 +86,10 @@ func PolicyExceptionLabel(exception kyvernov2beta1.PolicyException) string { return LabelPrefixPolicyException + exception.GetName() } +func ValidatingAdmissionPolicyBindingLabel(binding admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding) string { + return LabelPrefixValidatingAdmissionPolicyBinding + binding.GetName() +} + func CleanupKyvernoLabels(obj metav1.Object) { labels := obj.GetLabels() for key := range labels { @@ -144,6 +155,10 @@ func SetPolicyExceptionLabel(report kyvernov1alpha2.ReportInterface, exception k controllerutils.SetLabel(report, PolicyExceptionLabel(exception), exception.GetResourceVersion()) } +func SetValidatingAdmissionPolicyBindingLabel(report kyvernov1alpha2.ReportInterface, binding v1alpha1.ValidatingAdmissionPolicyBinding) { + controllerutils.SetLabel(report, ValidatingAdmissionPolicyBindingLabel(binding), binding.GetResourceVersion()) +} + func GetResourceUid(report metav1.Object) types.UID { return types.UID(controllerutils.GetLabel(report, LabelResourceUid)) } diff --git a/pkg/utils/report/results.go b/pkg/utils/report/results.go index 7d6a7c16c2..f1c6873e52 100644 --- a/pkg/utils/report/results.go +++ b/pkg/utils/report/results.go @@ -144,6 +144,11 @@ func EngineResponseToReportResults(response engineapi.EngineResponse) []policyre Seconds: time.Now().Unix(), }, } + if ruleResult.ValidatingAdmissionPolicyBinding() != nil { + result.Properties = map[string]string{ + "binding": ruleResult.ValidatingAdmissionPolicyBinding().Name, + } + } results = append(results, result) } } diff --git a/pkg/validatingadmissionpolicy/validatingadmissionpolicy.go b/pkg/validatingadmissionpolicy/validatingadmissionpolicy.go index 43bb4a61d1..5b66737206 100644 --- a/pkg/validatingadmissionpolicy/validatingadmissionpolicy.go +++ b/pkg/validatingadmissionpolicy/validatingadmissionpolicy.go @@ -126,16 +126,23 @@ func Validate(policyData PolicyData, resource unstructured.Unstructured, client a = admission.NewAttributesRecord(resource.DeepCopyObject(), nil, resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName(), gvr, "", admission.Create, nil, false, nil) resPath := fmt.Sprintf("%s/%s/%s", a.GetNamespace(), a.GetKind().Kind, a.GetName()) logger.V(3).Info("validate resource %s against policy %s", resPath, policy.GetName()) - return validateResource(policy, resource, a) + return validateResource(policy, nil, resource, a) } else { - for _, binding := range bindings { + for i, binding := range bindings { // convert policy binding from v1alpha1 to v1beta1 var namespaceSelector, objectSelector, paramSelector metav1.LabelSelector - if binding.Spec.MatchResources.NamespaceSelector != nil { - namespaceSelector = *binding.Spec.MatchResources.NamespaceSelector - } - if binding.Spec.MatchResources.ObjectSelector != nil { - objectSelector = *binding.Spec.MatchResources.ObjectSelector + var resourceRules, excludeResourceRules []v1alpha1.NamedRuleWithOperations + var matchPolicy *v1alpha1.MatchPolicyType + if binding.Spec.MatchResources != nil { + if binding.Spec.MatchResources.NamespaceSelector != nil { + namespaceSelector = *binding.Spec.MatchResources.NamespaceSelector + } + if binding.Spec.MatchResources.ObjectSelector != nil { + objectSelector = *binding.Spec.MatchResources.ObjectSelector + } + resourceRules = binding.Spec.MatchResources.ResourceRules + excludeResourceRules = binding.Spec.MatchResources.ExcludeResourceRules + matchPolicy = binding.Spec.MatchResources.MatchPolicy } var paramRef v1beta1.ParamRef @@ -157,9 +164,9 @@ func Validate(policyData PolicyData, resource unstructured.Unstructured, client MatchResources: &v1beta1.MatchResources{ NamespaceSelector: &namespaceSelector, ObjectSelector: &objectSelector, - ResourceRules: convertRules(binding.Spec.MatchResources.ResourceRules), - ExcludeResourceRules: convertRules(binding.Spec.MatchResources.ExcludeResourceRules), - MatchPolicy: (*v1beta1.MatchPolicyType)(binding.Spec.MatchResources.MatchPolicy), + ResourceRules: convertRules(resourceRules), + ExcludeResourceRules: convertRules(excludeResourceRules), + MatchPolicy: (*v1beta1.MatchPolicyType)(matchPolicy), }, ValidationActions: convertValidationActions(binding.Spec.ValidationActions), }, @@ -174,20 +181,20 @@ func Validate(policyData PolicyData, resource unstructured.Unstructured, client resPath := fmt.Sprintf("%s/%s/%s", a.GetNamespace(), a.GetKind().Kind, a.GetName()) logger.V(3).Info("validate resource %s against policy %s with binding %s", resPath, policy.GetName(), binding.GetName()) - return validateResource(policy, resource, a) + return validateResource(policy, &bindings[i], resource, a) } } } else { a = admission.NewAttributesRecord(resource.DeepCopyObject(), nil, resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName(), gvr, "", admission.Create, nil, false, nil) resPath := fmt.Sprintf("%s/%s/%s", a.GetNamespace(), a.GetKind().Kind, a.GetName()) logger.V(3).Info("validate resource %s against policy %s", resPath, policy.GetName()) - return validateResource(policy, resource, a) + return validateResource(policy, nil, resource, a) } return engineResponse, nil } -func validateResource(policy v1alpha1.ValidatingAdmissionPolicy, resource unstructured.Unstructured, a admission.Attributes) (engineapi.EngineResponse, error) { +func validateResource(policy v1alpha1.ValidatingAdmissionPolicy, binding *v1alpha1.ValidatingAdmissionPolicyBinding, resource unstructured.Unstructured, a admission.Attributes) (engineapi.EngineResponse, error) { startTime := time.Now() engineResponse := engineapi.NewEngineResponse(resource, engineapi.NewValidatingAdmissionPolicy(policy), nil) @@ -244,6 +251,10 @@ func validateResource(policy v1alpha1.ValidatingAdmissionPolicy, resource unstru if isPass { ruleResp = engineapi.RulePass(policy.GetName(), engineapi.Validation, "") } + + if binding != nil { + ruleResp = ruleResp.WithBinding(binding) + } policyResp.Add(engineapi.NewExecutionStats(startTime, time.Now()), *ruleResp) engineResponse = engineResponse.WithPolicyResponse(policyResp) diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/README.md b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/README.md new file mode 100644 index 0000000000..5be2b51ed4 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/README.md @@ -0,0 +1,11 @@ +## Description + +This test checks that policy reports are generated successfully as a result of applying the ValidatingAdmissionPolicy with its binding to a resource. + +## Steps + +1. - Create a `staging-ns-2` namespace whose label is `environment: staging-ns-2` +1. - Create a Deployment named `deployment-3` with 7 replicas in the `staging-ns-2` namespace. +1. - Create a ValidatingAdmissionPolicy that checks deployment replicas to be less than or equal to 5. + - Create a ValidatingAdmissionPolicyBinding that matches resources whose namespace has a label of `environment: staging`. +1. - A policy report is generated for `deployment-3` with a fail result. diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/chainsaw-test.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/chainsaw-test.yaml new file mode 100755 index 0000000000..c2dbffd18b --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/chainsaw-test.yaml @@ -0,0 +1,27 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: with-validating-admission-policy-binding-fail +spec: + steps: + - name: step-01 + try: + - apply: + file: ns.yaml + - assert: + file: ns.yaml + - name: step-02 + try: + - apply: + file: deployment.yaml + - assert: + file: deployment-assert.yaml + - name: step-03 + try: + - apply: + file: policy.yaml + - name: step-04 + try: + - assert: + file: report-assert.yaml diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/deployment-assert.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/deployment-assert.yaml new file mode 100644 index 0000000000..1f379df449 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/deployment-assert.yaml @@ -0,0 +1,5 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-3 + namespace: staging-ns-2 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/deployment.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/deployment.yaml new file mode 100644 index 0000000000..13c3517883 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-3 + namespace: staging-ns-2 + labels: + app: nginx +spec: + replicas: 7 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/ns.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/ns.yaml new file mode 100644 index 0000000000..dcd49e7623 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/ns.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: staging-ns-2 + labels: + environment: staging-ns-2 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/policy.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/policy.yaml new file mode 100644 index 0000000000..67e92df511 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/policy.yaml @@ -0,0 +1,30 @@ +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: ValidatingAdmissionPolicy +metadata: + name: "check-deployment-replicas-04" +spec: + matchConstraints: + resourceRules: + - apiGroups: + - apps + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - deployments + validations: + - expression: object.spec.replicas <= 5 +--- +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: "check-deployment-replicas-binding-04" +spec: + policyName: "check-deployment-replicas-04" + validationActions: [Deny] + matchResources: + namespaceSelector: + matchLabels: + environment: staging-ns-2 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/report-assert.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/report-assert.yaml new file mode 100644 index 0000000000..46203bc682 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-fail/report-assert.yaml @@ -0,0 +1,26 @@ +apiVersion: wgpolicyk8s.io/v1alpha2 +kind: PolicyReport +metadata: + labels: + app.kubernetes.io/managed-by: kyverno + namespace: staging-ns-2 + ownerReferences: + - apiVersion: apps/v1 + kind: Deployment + name: deployment-3 +results: +- message: 'failed expression: object.spec.replicas <= 5' + policy: check-deployment-replicas-04 + result: fail + source: ValidatingAdmissionPolicy +scope: + apiVersion: apps/v1 + kind: Deployment + name: deployment-3 + namespace: staging-ns-2 +summary: + error: 0 + fail: 1 + pass: 0 + skip: 0 + warn: 0 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/README.md b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/README.md new file mode 100644 index 0000000000..2dfb68c165 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/README.md @@ -0,0 +1,11 @@ +## Description + +This test checks that policy reports are generated successfully as a result of applying the ValidatingAdmissionPolicy with its binding to a resource. + +## Steps + +1. - Create a Deployment named `deployment-1` with 7 replicas in the `default` namespace. + - Create a Deployment named `deployment-2` with 3 replicas in the `default` namespace. +1. - Create a ValidatingAdmissionPolicy that checks deployment replicas to be less than or equal to 5. + - Create a ValidatingAdmissionPolicyBinding that matches resources whose namespace has a label of `environment: staging`. +1. - No policy reports generated for both `deployment-1` and `deployment-2`. diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/chainsaw-test.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/chainsaw-test.yaml new file mode 100755 index 0000000000..30cb6d8d4f --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/chainsaw-test.yaml @@ -0,0 +1,21 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: with-validating-admission-policy-binding-no-reports +spec: + steps: + - name: step-01 + try: + - apply: + file: deployment.yaml + - assert: + file: deployment-assert.yaml + - name: step-02 + try: + - apply: + file: policy.yaml + - name: step-03 + try: + - error: + file: report-error.yaml diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/deployment-assert.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/deployment-assert.yaml new file mode 100644 index 0000000000..6004bf3b83 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/deployment-assert.yaml @@ -0,0 +1,11 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-1 + namespace: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-2 + namespace: default diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/deployment.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/deployment.yaml new file mode 100644 index 0000000000..29ab9f4fcd --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/deployment.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-1 + namespace: default + labels: + app: nginx +spec: + replicas: 7 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-2 + namespace: default + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/policy.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/policy.yaml new file mode 100644 index 0000000000..ea770c52ea --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/policy.yaml @@ -0,0 +1,30 @@ +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: ValidatingAdmissionPolicy +metadata: + name: "check-deployment-replicas-05" +spec: + matchConstraints: + resourceRules: + - apiGroups: + - apps + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - deployments + validations: + - expression: object.spec.replicas <= 5 +--- +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: "check-deployment-replicas-binding-05" +spec: + policyName: "check-deployment-replicas-05" + validationActions: [Deny] + matchResources: + namespaceSelector: + matchLabels: + environment: staging diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/report-error.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/report-error.yaml new file mode 100644 index 0000000000..9e4ad5952f --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-no-reports/report-error.yaml @@ -0,0 +1,52 @@ +apiVersion: wgpolicyk8s.io/v1alpha2 +kind: PolicyReport +metadata: + labels: + app.kubernetes.io/managed-by: kyverno + namespace: default + ownerReferences: + - apiVersion: apps/v1 + kind: Deployment + name: deployment-1 +results: +- message: 'failed expression: object.spec.replicas <= 5' + policy: check-deployment-replicas-05 + result: fail + source: ValidatingAdmissionPolicy +scope: + apiVersion: apps/v1 + kind: Deployment + name: deployment-1 + namespace: default +summary: + error: 0 + fail: 1 + pass: 0 + skip: 0 + warn: 0 +--- +apiVersion: wgpolicyk8s.io/v1alpha2 +kind: PolicyReport +metadata: + labels: + app.kubernetes.io/managed-by: kyverno + namespace: default + ownerReferences: + - apiVersion: apps/v1 + kind: Deployment + name: deployment-2 +results: +- policy: check-deployment-replicas-05 + result: pass + source: ValidatingAdmissionPolicy +scope: + apiVersion: apps/v1 + kind: Deployment + name: deployment-2 + namespace: default +summary: + error: 0 + fail: 0 + pass: 1 + skip: 0 + warn: 0 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/README.md b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/README.md new file mode 100644 index 0000000000..85eb6033c1 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/README.md @@ -0,0 +1,11 @@ +## Description + +This test checks that policy reports are generated successfully as a result of applying the ValidatingAdmissionPolicy with its binding to a resource. + +## Steps + +1. - Create a `staging-ns-1` namespace whose label is `environment: staging-ns-1` +1. - Create a Deployment named `deployment-4` with 3 replicas in the `staging-ns-1` namespace. +1. - Create a ValidatingAdmissionPolicy that checks deployment replicas to be less than or equal to 5. + - Create a ValidatingAdmissionPolicyBinding that matches resources whose namespace has a label of `environment: staging`. +1. - A policy report is generated for `deployment-4` with a pass result. diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/chainsaw-test.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/chainsaw-test.yaml new file mode 100755 index 0000000000..8caaf51ab6 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/chainsaw-test.yaml @@ -0,0 +1,27 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: with-validating-admission-policy-binding-pass +spec: + steps: + - name: step-01 + try: + - apply: + file: ns.yaml + - assert: + file: ns.yaml + - name: step-02 + try: + - apply: + file: deployment.yaml + - assert: + file: deployment-assert.yaml + - name: step-03 + try: + - apply: + file: policy.yaml + - name: step-04 + try: + - assert: + file: report-assert.yaml diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/deployment-assert.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/deployment-assert.yaml new file mode 100644 index 0000000000..d754d40f09 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/deployment-assert.yaml @@ -0,0 +1,5 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-4 + namespace: staging-ns-1 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/deployment.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/deployment.yaml new file mode 100644 index 0000000000..0740d8cbbb --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-4 + namespace: staging-ns-1 + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/ns.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/ns.yaml new file mode 100644 index 0000000000..69d88202eb --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/ns.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: staging-ns-1 + labels: + environment: staging-ns-1 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/policy.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/policy.yaml new file mode 100644 index 0000000000..bc93619673 --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/policy.yaml @@ -0,0 +1,30 @@ +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: ValidatingAdmissionPolicy +metadata: + name: "check-deployment-replicas-03" +spec: + matchConstraints: + resourceRules: + - apiGroups: + - apps + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - deployments + validations: + - expression: object.spec.replicas <= 5 +--- +apiVersion: admissionregistration.k8s.io/v1alpha1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: "check-deployment-replicas-binding-03" +spec: + policyName: "check-deployment-replicas-03" + validationActions: [Deny] + matchResources: + namespaceSelector: + matchLabels: + environment: staging-ns-1 diff --git a/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/report-assert.yaml b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/report-assert.yaml new file mode 100644 index 0000000000..4b46c7d28e --- /dev/null +++ b/test/conformance/chainsaw/validating-admission-policy-reports/background/with-validating-admission-policy-binding-pass/report-assert.yaml @@ -0,0 +1,25 @@ +apiVersion: wgpolicyk8s.io/v1alpha2 +kind: PolicyReport +metadata: + labels: + app.kubernetes.io/managed-by: kyverno + namespace: staging-ns-1 + ownerReferences: + - apiVersion: apps/v1 + kind: Deployment + name: deployment-4 +results: +- policy: check-deployment-replicas-03 + result: pass + source: ValidatingAdmissionPolicy +scope: + apiVersion: apps/v1 + kind: Deployment + name: deployment-4 + namespace: staging-ns-1 +summary: + error: 0 + fail: 0 + pass: 1 + skip: 0 + warn: 0