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

fix: allow changes to preexisting resources that violate a validate foreach, cel or pss policy (#10033)

* feat: allow changes to preexisting resources that violate a validate foreach, cel or pss policy

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: do old object verification as create operation

this fixes the case where we are checking request.operation in a deny condition

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: update the json context in set operation

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: typo

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: update error message

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: add match and exclude check

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: match exclude in if

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* feat: add option to disable validation of old object

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: unit tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* feat: chainsaw tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: update readme

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: conflicts

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: chainsaw tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: ci

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: nil ptr error

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: linter

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: linter

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* feat: old obj verification in assert

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: codegen

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* feat: chainsaw tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* feat: chainsaw test for assert

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: cleanup

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: chainsaw tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: pss

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* feat: common functions for allow existing violations

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: types

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: typos

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: pss old resource

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* feat: chainsaw test for PSS

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: use old objects

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: more merge changes

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: e2e matrxix

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: refactor and dont return error when old obj validation fails

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: return resp when not matched

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: add logs and return skip when old object validation fails

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* Update validate_resource.go

Co-authored-by: shuting <shutting06@gmail.com>
Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* Update validate_pss.go

Co-authored-by: shuting <shutting06@gmail.com>
Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* Update validate_assert.go

Co-authored-by: shuting <shutting06@gmail.com>
Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

---------

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
Co-authored-by: Jim Bugwadia <jim@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
Co-authored-by: shuting <shutting06@gmail.com>
This commit is contained in:
Vishal Choudhary 2024-09-06 12:12:56 +05:30 committed by GitHub
parent 02e27ec3d4
commit 1ef9b876e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 1269 additions and 81 deletions

View file

@ -473,6 +473,11 @@ type Validation struct {
// +optional
FailureActionOverrides []ValidationFailureActionOverride `json:"failureActionOverrides,omitempty"`
// AllowExistingViolations allows prexisting violating resources to continue violating a policy.
// +kubebuilder:validation:Optional
// +kubebuilder:default=true
AllowExistingViolations *bool `json:"allowExistingViolations,omitempty" yaml:"allowExistingViolations,omitempty"`
// Message specifies a custom message to be displayed on failure.
// +optional
Message string `json:"message,omitempty"`

View file

@ -187,6 +187,17 @@ func (r *Rule) HasValidate() bool {
return !datautils.DeepEqual(r.Validation, Validation{})
}
// HasValidateAllowExistingViolations() checks for allowExisitingViolations under validate rule
func (r *Rule) HasValidateAllowExistingViolations() bool {
var allowExisitingViolations bool
if r.Validation.AllowExistingViolations == nil {
allowExisitingViolations = true
} else {
allowExisitingViolations = *r.Validation.AllowExistingViolations
}
return allowExisitingViolations
}
// HasGenerate checks for generate rule
func (r *Rule) HasGenerate() bool {
return !datautils.DeepEqual(r.Generation, Generation{})

View file

@ -1698,6 +1698,11 @@ func (in *Validation) DeepCopyInto(out *Validation) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AllowExistingViolations != nil {
in, out := &in.AllowExistingViolations, &out.AllowExistingViolations
*out = new(bool)
**out = **in
}
if in.Manifests != nil {
in, out := &in.Manifests, &out.Manifests
*out = new(Manifests)

View file

@ -2888,6 +2888,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting violating
resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -7852,6 +7857,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -17553,6 +17563,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns

View file

@ -2889,6 +2889,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting violating
resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -7854,6 +7859,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -17556,6 +17566,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns

View file

@ -2882,6 +2882,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting violating
resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -7846,6 +7851,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -17547,6 +17557,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns

View file

@ -2883,6 +2883,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting violating
resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -7848,6 +7853,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -17550,6 +17560,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns

View file

@ -2882,6 +2882,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting violating
resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -7846,6 +7851,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -17547,6 +17557,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns

View file

@ -2883,6 +2883,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting violating
resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -7848,6 +7853,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -17550,6 +17560,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns

View file

@ -8205,6 +8205,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting violating
resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -13169,6 +13174,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -22870,6 +22880,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -28163,6 +28178,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting violating
resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -33128,6 +33148,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns
@ -42830,6 +42855,11 @@ spec:
validate:
description: Validation is used to validate matching resources.
properties:
allowExistingViolations:
default: true
description: AllowExistingViolations allows prexisting
violating resources to continue violating a policy.
type: boolean
anyPattern:
description: |-
AnyPattern specifies list of validation patterns. At least one of the patterns

View file

@ -4802,6 +4802,17 @@ namespace-wise. It overrides FailureAction for the specified namespaces.</p>
</tr>
<tr>
<td>
<code>allowExistingViolations</code><br/>
<em>
bool
</em>
</td>
<td>
<p>AllowExistingViolations allows prexisting violating resources to continue violating a policy.</p>
</td>
</tr>
<tr>
<td>
<code>message</code><br/>
<em>
string

View file

@ -9669,6 +9669,35 @@ namespace-wise. It overrides FailureAction for the specified namespaces.</p>
<tr>
<td><code>allowExistingViolations</code>
<span style="color:blue;"> *</span>
</br>
<span style="font-family: monospace">bool</span>
</td>
<td>
<p>AllowExistingViolations allows prexisting violating resources to continue violating a policy.</p>
</td>
</tr>
<tr>
<td><code>message</code>

View file

@ -27,17 +27,18 @@ import (
// ValidationApplyConfiguration represents an declarative configuration of the Validation type for use
// with apply.
type ValidationApplyConfiguration struct {
FailureAction *v1.ValidationFailureAction `json:"failureAction,omitempty"`
FailureActionOverrides []ValidationFailureActionOverrideApplyConfiguration `json:"failureActionOverrides,omitempty"`
Message *string `json:"message,omitempty"`
Manifests *ManifestsApplyConfiguration `json:"manifests,omitempty"`
ForEachValidation []ForEachValidationApplyConfiguration `json:"foreach,omitempty"`
RawPattern *apiextensionsv1.JSON `json:"pattern,omitempty"`
RawAnyPattern *apiextensionsv1.JSON `json:"anyPattern,omitempty"`
Deny *DenyApplyConfiguration `json:"deny,omitempty"`
PodSecurity *PodSecurityApplyConfiguration `json:"podSecurity,omitempty"`
CEL *CELApplyConfiguration `json:"cel,omitempty"`
Assert *v1alpha1.Any `json:"assert,omitempty"`
FailureAction *v1.ValidationFailureAction `json:"failureAction,omitempty"`
FailureActionOverrides []ValidationFailureActionOverrideApplyConfiguration `json:"failureActionOverrides,omitempty"`
AllowExistingViolations *bool `json:"allowExistingViolations,omitempty"`
Message *string `json:"message,omitempty"`
Manifests *ManifestsApplyConfiguration `json:"manifests,omitempty"`
ForEachValidation []ForEachValidationApplyConfiguration `json:"foreach,omitempty"`
RawPattern *apiextensionsv1.JSON `json:"pattern,omitempty"`
RawAnyPattern *apiextensionsv1.JSON `json:"anyPattern,omitempty"`
Deny *DenyApplyConfiguration `json:"deny,omitempty"`
PodSecurity *PodSecurityApplyConfiguration `json:"podSecurity,omitempty"`
CEL *CELApplyConfiguration `json:"cel,omitempty"`
Assert *v1alpha1.Any `json:"assert,omitempty"`
}
// ValidationApplyConfiguration constructs an declarative configuration of the Validation type for use with
@ -67,6 +68,14 @@ func (b *ValidationApplyConfiguration) WithFailureActionOverrides(values ...*Val
return b
}
// WithAllowExistingViolations sets the AllowExistingViolations field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the AllowExistingViolations field is set to the value of the last call.
func (b *ValidationApplyConfiguration) WithAllowExistingViolations(value bool) *ValidationApplyConfiguration {
b.AllowExistingViolations = &value
return b
}
// WithMessage sets the Message field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Message field is set to the value of the last call.

View file

@ -19,6 +19,7 @@ type PolicyContext interface {
SetResources(oldResource, newResource unstructured.Unstructured) error
AdmissionInfo() kyvernov2.RequestInfo
Operation() kyvernov1.AdmissionOperation
SetOperation(op kyvernov1.AdmissionOperation) error
NamespaceLabels() map[string]string
RequestResource() metav1.GroupVersionResource
ResourceKind() (schema.GroupVersionKind, string)

View file

@ -0,0 +1,46 @@
package validation
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
"github.com/kyverno/kyverno/pkg/utils/match"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func matchResource(resource unstructured.Unstructured, rule kyvernov1.Rule) bool {
if rule.MatchResources.All != nil || rule.MatchResources.Any != nil {
matched := match.CheckMatchesResources(
resource,
kyvernov2beta1.MatchResources{
Any: rule.MatchResources.Any,
All: rule.MatchResources.All,
},
make(map[string]string),
kyvernov2.RequestInfo{},
resource.GroupVersionKind(),
"",
)
if matched != nil {
return false
}
}
if rule.ExcludeResources.All != nil || rule.ExcludeResources.Any != nil {
excluded := match.CheckMatchesResources(
resource,
kyvernov2beta1.MatchResources{
Any: rule.ExcludeResources.Any,
All: rule.ExcludeResources.All,
},
make(map[string]string),
kyvernov2.RequestInfo{},
resource.GroupVersionKind(),
"",
)
if excluded == nil {
return false
}
}
return true
}

View file

@ -17,6 +17,7 @@ import (
"github.com/kyverno/kyverno/pkg/engine/handlers"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/client-go/tools/cache"
)
@ -111,6 +112,20 @@ func (h validateAssertHandler) Process(
}
// compose a response
if len(errs) != 0 {
allowExisitingViolations := rule.HasValidateAllowExistingViolations()
if engineutils.IsUpdateRequest(policyContext) && allowExisitingViolations {
errs, err := validateOldObject(ctx, policyContext, rule, payload, bindings)
if err != nil {
logger.V(2).Info("warning: failed to validate old object, skipping the rule evaluation as pre-existing violations are allowed", "rule", rule.Name, "error", err.Error())
return resource, handlers.WithSkip(rule, engineapi.Validation, "failed to validate old object, skipping as preexisting violations are allowed")
}
logger.V(3).Info("old object verification", "errors", errs)
if len(errs) != 0 {
logger.V(3).Info("skipping modified resource as validation results have not changed")
return resource, handlers.WithSkip(rule, engineapi.Validation, "skipping modified resource as validation results have not changed")
}
}
var responses []*engineapi.RuleResponse
for _, err := range errs {
responses = append(responses, engineapi.RuleFail(rule.Name, engineapi.Validation, err.Error(), rule.ReportProperties))
@ -122,3 +137,26 @@ func (h validateAssertHandler) Process(
engineapi.RulePass(rule.Name, engineapi.Validation, msg, rule.ReportProperties),
)
}
func validateOldObject(ctx context.Context, policyContext engineapi.PolicyContext, rule kyvernov1.Rule, payload map[string]any, bindings binding.Bindings) (field.ErrorList, error) {
if policyContext.Operation() != kyvernov1.Update {
return nil, nil
}
oldResource := policyContext.OldResource()
if ok := matchResource(oldResource, rule); !ok {
return nil, nil
}
payload["object"] = policyContext.OldResource().Object
payload["oldObject"] = nil
payload["operation"] = kyvernov1.Create
asserttion := assert.Parse(ctx, rule.Validation.Assert.Value)
errs, err := assert.Assert(ctx, nil, asserttion, payload, bindings)
if err != nil {
return nil, fmt.Errorf("failed to apply assertion: %w", err)
}
return errs, nil
}

View file

@ -16,6 +16,7 @@ import (
"github.com/kyverno/kyverno/pkg/pss"
pssutils "github.com/kyverno/kyverno/pkg/pss/utils"
"github.com/kyverno/kyverno/pkg/utils/api"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
@ -36,9 +37,22 @@ func (h validatePssHandler) Process(
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
_ engineapi.EngineContextLoader,
engineLoader engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
resource, ruleResp := h.validate(ctx, logger, policyContext, resource, rule, engineLoader, exceptions)
return resource, handlers.WithResponses(ruleResp)
}
func (h validatePssHandler) validate(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
engineLoader engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (unstructured.Unstructured, *engineapi.RuleResponse) {
if engineutils.IsDeleteRequest(policyContext) {
logger.V(3).Info("skipping PSS validation on deleted resource")
return resource, nil
@ -62,12 +76,10 @@ func (h validatePssHandler) Process(
key, err := cache.MetaNamespaceKeyFunc(&polex)
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", polex.GetNamespace(), "name", polex.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
return resource, engineapi.RuleError(rule.Name, engineapi.Validation, "failed to compute exception key", err, rule.ReportProperties)
}
logger.V(3).Info("policy rule is skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exception "+key, rule.ReportProperties).WithExceptions([]kyvernov2.PolicyException{polex}),
)
return resource, engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exception "+key, rule.ReportProperties).WithExceptions([]kyvernov2.PolicyException{polex})
}
}
@ -78,7 +90,7 @@ func (h validatePssHandler) Process(
}
podSpec, metadata, err := getSpec(resource)
if err != nil {
return resource, handlers.WithError(rule, engineapi.Validation, "Error while getting new resource", err)
return resource, engineapi.RuleError(rule.Name, engineapi.Validation, "Error while getting new resource", err, rule.ReportProperties)
}
pod := &corev1.Pod{
Spec: *podSpec,
@ -86,7 +98,7 @@ func (h validatePssHandler) Process(
}
levelVersion, err := pss.ParseVersion(podSecurity.Level, podSecurity.Version)
if err != nil {
return resource, handlers.WithError(rule, engineapi.Validation, "failed to parse pod security api version", err)
return resource, engineapi.RuleError(rule.Name, engineapi.Validation, "failed to parse pod security api version", err, rule.ReportProperties)
}
allowed, pssChecks := pss.EvaluatePod(levelVersion, podSecurity.Exclude, pod)
pssChecks = convertChecks(pssChecks, resource.GetKind())
@ -98,9 +110,7 @@ func (h validatePssHandler) Process(
}
if allowed {
msg := fmt.Sprintf("Validation rule '%s' passed.", rule.Name)
return resource, handlers.WithResponses(
engineapi.RulePass(rule.Name, engineapi.Validation, msg, rule.ReportProperties).WithPodSecurityChecks(podSecurityChecks),
)
return resource, engineapi.RulePass(rule.Name, engineapi.Validation, msg, rule.ReportProperties).WithPodSecurityChecks(podSecurityChecks)
} else {
// apply pod security exceptions if exist
var excludes []kyvernov1.PodSecurityStandard
@ -109,7 +119,7 @@ func (h validatePssHandler) Process(
key, err := cache.MetaNamespaceKeyFunc(&matchedExceptions[i])
if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err)
return resource, engineapi.RuleError(rule.Name, engineapi.Validation, "failed to compute exception key", err, rule.ReportProperties)
}
keys = append(keys, key)
excludes = append(excludes, exception.Spec.PodSecurity...)
@ -119,17 +129,73 @@ func (h validatePssHandler) Process(
if len(pssChecks) == 0 && err == nil {
podSecurityChecks.Checks = pssChecks
logger.V(3).Info("policy rule is skipped due to policy exceptions", "exceptions", keys)
return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions "+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions).WithPodSecurityChecks(podSecurityChecks),
)
return resource, engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule is skipped due to policy exceptions "+strings.Join(keys, ", "), rule.ReportProperties).WithExceptions(matchedExceptions).WithPodSecurityChecks(podSecurityChecks)
}
msg := fmt.Sprintf(`Validation rule '%s' failed. It violates PodSecurity "%s:%s": %s`, rule.Name, podSecurity.Level, podSecurity.Version, pss.FormatChecksPrint(pssChecks))
return resource, handlers.WithResponses(
engineapi.RuleFail(rule.Name, engineapi.Validation, msg, rule.ReportProperties).WithPodSecurityChecks(podSecurityChecks),
)
ruleResponse := engineapi.RuleFail(rule.Name, engineapi.Validation, msg, rule.ReportProperties).WithPodSecurityChecks(podSecurityChecks)
allowExisitingViolations := rule.HasValidateAllowExistingViolations()
if engineutils.IsUpdateRequest(policyContext) && allowExisitingViolations {
logger.V(4).Info("is update request")
priorResp, err := h.validateOldObject(ctx, logger, policyContext, resource, rule, engineLoader, exceptions)
if err != nil {
logger.V(2).Info("warning: failed to validate old object, skipping the rule evaluation as pre-existing violations are allowed", "rule", rule.Name, "error", err.Error())
return resource, engineapi.RuleSkip(rule.Name, engineapi.Validation, "failed to validate old object, skipping as preexisting violations are allowed", rule.ReportProperties)
}
if ruleResponse.Status() == priorResp.Status() {
logger.V(3).Info("skipping modified resource as validation results have not changed", "oldResp", priorResp, "newResp", ruleResponse)
if ruleResponse.Status() == engineapi.RuleStatusPass {
return resource, ruleResponse
}
return resource, engineapi.RuleSkip(rule.Name, engineapi.Validation, "skipping modified resource as validation results have not changed", rule.ReportProperties)
}
logger.V(4).Info("old object response is different", "oldResp", priorResp, "newResp", ruleResponse)
}
return resource, ruleResponse
}
}
func (h validatePssHandler) validateOldObject(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
engineLoader engineapi.EngineContextLoader,
exceptions []*kyvernov2.PolicyException,
) (*engineapi.RuleResponse, error) {
if policyContext.Operation() != kyvernov1.Update {
return nil, nil
}
newResource := policyContext.NewResource()
oldResource := policyContext.OldResource()
emptyResource := unstructured.Unstructured{}
if ok := matchResource(oldResource, rule); !ok {
return nil, nil
}
if err := policyContext.SetResources(emptyResource, oldResource); err != nil {
return nil, errors.Wrapf(err, "failed to set resources")
}
if err := policyContext.SetOperation(kyvernov1.Create); err != nil { // simulates the condition when old object was "created"
return nil, errors.Wrapf(err, "failed to set operation")
}
_, resp := h.validate(ctx, logger, policyContext, oldResource, rule, engineLoader, exceptions)
if err := policyContext.SetResources(oldResource, newResource); err != nil {
return nil, errors.Wrapf(err, "failed to reset resources")
}
if err := policyContext.SetOperation(kyvernov1.Update); err != nil {
return nil, errors.Wrapf(err, "failed to reset operation")
}
return resp, nil
}
func convertChecks(checks []pssutils.PSSCheckResult, kind string) (newChecks []pssutils.PSSCheckResult) {
if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" {
for i := range checks {

View file

@ -131,42 +131,39 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, s, v.rule.ReportProperties)
}
var ruleResponse *engineapi.RuleResponse
if v.deny != nil {
return v.validateDeny()
}
if v.pattern != nil || v.anyPattern != nil {
ruleResponse = v.validateDeny()
} else if v.pattern != nil || v.anyPattern != nil {
if err = v.substitutePatterns(); err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "variable substitution failed", err, v.rule.ReportProperties)
}
ruleResponse := v.validateResourceWithRule()
ruleResponse = v.validateResourceWithRule()
} else if v.forEach != nil {
ruleResponse = v.validateForEach(ctx)
} else {
v.log.V(2).Info("invalid validation rule: podSecurity, cel, patterns, or deny expected")
}
if engineutils.IsUpdateRequest(v.policyContext) {
priorResp, err := v.validateOldObject(ctx)
if err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to validate old object", err, v.rule.ReportProperties)
}
if engineutils.IsSameRuleResponse(ruleResponse, priorResp) {
v.log.V(3).Info("skipping modified resource as validation results have not changed")
if ruleResponse.Status() == engineapi.RuleStatusPass {
return ruleResponse
}
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "skipping modified resource as validation results have not changed", v.rule.ReportProperties)
}
allowExisitingViolations := v.rule.HasValidateAllowExistingViolations()
if engineutils.IsUpdateRequest(v.policyContext) && allowExisitingViolations && v.nesting == 0 { // is update request and is the root level validate
priorResp, err := v.validateOldObject(ctx)
if err != nil {
v.log.V(2).Info("warning: failed to validate old object, skipping the rule evaluation as pre-existing violations are allowed", "rule", v.rule.Name, "error", err.Error())
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "failed to validate old object, skipping as preexisting violations are allowed", ruleResponse.Properties())
}
return ruleResponse
if engineutils.IsSameRuleResponse(ruleResponse, priorResp) {
v.log.V(3).Info("skipping modified resource as validation results have not changed")
if ruleResponse.Status() == engineapi.RuleStatusPass {
return ruleResponse
}
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "skipping modified resource as validation results have not changed", v.rule.ReportProperties)
}
}
if v.forEach != nil {
ruleResponse := v.validateForEach(ctx)
return ruleResponse
}
v.log.V(2).Info("invalid validation rule: podSecurity, cel, patterns, or deny expected")
return nil
return ruleResponse
}
func (v *validator) validateOldObject(ctx context.Context) (*engineapi.RuleResponse, error) {
@ -178,9 +175,16 @@ func (v *validator) validateOldObject(ctx context.Context) (*engineapi.RuleRespo
oldResource := v.policyContext.OldResource()
emptyResource := unstructured.Unstructured{}
if ok := matchResource(oldResource, v.rule); !ok {
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "resource not matched", v.rule.ReportProperties), nil
}
if err := v.policyContext.SetResources(emptyResource, oldResource); err != nil {
return nil, errors.Wrapf(err, "failed to set resources")
}
if err := v.policyContext.SetOperation(kyvernov1.Create); err != nil { // simulates the condition when old object was "created"
return nil, errors.Wrapf(err, "failed to set operation")
}
resp := v.validate(ctx)
@ -188,6 +192,10 @@ func (v *validator) validateOldObject(ctx context.Context) (*engineapi.RuleRespo
return nil, errors.Wrapf(err, "failed to reset resources")
}
if err := v.policyContext.SetOperation(kyvernov1.Update); err != nil {
return nil, errors.Wrapf(err, "failed to reset operation")
}
return resp, nil
}

View file

@ -16,7 +16,7 @@ func Test_validateOldObject(t *testing.T) {
return nil
}
policyContext := buildTestNamespaceLabelsContext(t)
policyContext := buildTestNamespaceLabelsContext(t, validateDenyPolicy, resource, oldResource)
rule := policyContext.Policy().GetSpec().Rules[0]
v := newValidator(logr.Discard(), mockCL, policyContext, rule)
@ -32,8 +32,27 @@ func Test_validateOldObject(t *testing.T) {
assert.Equal(t, api.RuleStatusFail, resp2.Status())
}
func buildTestNamespaceLabelsContext(t *testing.T) api.PolicyContext {
policy := `{
func buildTestNamespaceLabelsContext(t *testing.T, policy string, resource string, oldResource string) api.PolicyContext {
return buildContext(t, kyvernov1.Update, policy, resource, oldResource)
}
func Test_validateOldObjectForeach(t *testing.T) {
mockCL := func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error {
return nil
}
policyContext := buildTestNamespaceLabelsContext(t, validateForeachPolicy, resource, oldResource)
rule := policyContext.Policy().GetSpec().Rules[0]
v := newValidator(logr.Discard(), mockCL, policyContext, rule)
ctx := context.TODO()
resp := v.validate(ctx)
assert.NotNil(t, resp)
assert.Equal(t, api.RuleStatusSkip, resp.Status())
}
var (
validateDenyPolicy = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
@ -105,11 +124,61 @@ func buildTestNamespaceLabelsContext(t *testing.T) api.PolicyContext {
}
]
}
}`
}`
resource := `{
validateForeachPolicy = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "validate-image-list"
},
"spec": {
"admission": true,
"background": true,
"rules": [
{
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"name": "check-image",
"validate": {
"failureAction": "Audit",
"allowExistingViolations": true,
"foreach": [
{
"deny": {
"conditions": {
"all": [
{
"key": "{{ element }}",
"operator": "NotEquals",
"value": "ghcr.io"
}
]
}
},
"list": "request.object.spec.containers[].image"
}
],
"message": "images must begin with ghcr.io"
}
}
]
}
}
`
resource = `{
"apiVersion": "v1",
"kind": "Namespace",
"kind": "Pod",
"metadata": {
"annotations": {},
"labels": {
@ -118,12 +187,49 @@ func buildTestNamespaceLabelsContext(t *testing.T) api.PolicyContext {
},
"name": "test"
},
"spec": {}
}`
"spec": {
"containers": [
{
"image": "ghcr.io/test-webserver",
"name": "test1",
"volumeMounts": [
{
"mountPath": "/tmp/cache",
"name": "cache-volume"
}
]
},
{
"image": "ghcr.io/test-webserver",
"name": "test2",
"volumeMounts": [
{
"mountPath": "/tmp/cache",
"name": "cache-volume"
},
{
"mountPath": "/gce",
"name": "gce"
}
]
}
],
"volumes": [
{
"name": "cache-volume",
"emptyDir": {}
},
{
"name": "gce",
"gcePersistentDisk": {}
}
]
}
}`
oldResource := `{
oldResource = `{
"apiVersion": "v1",
"kind": "Namespace",
"kind": "Pod",
"metadata": {
"labels": {
"kubernetes.io/metadata.name": "test",
@ -131,8 +237,43 @@ func buildTestNamespaceLabelsContext(t *testing.T) api.PolicyContext {
},
"name": "test"
},
"spec": {}
}`
return buildContext(t, kyvernov1.Update, policy, resource, oldResource)
}
"spec": {
"containers": [
{
"image": "ghcr.io/test-webserver",
"name": "test1",
"volumeMounts": [
{
"mountPath": "/tmp/cache",
"name": "cache-volume"
}
]
},
{
"image": "ghcr.io/test-webserver",
"name": "test2",
"volumeMounts": [
{
"mountPath": "/tmp/cache",
"name": "cache-volume"
},
{
"mountPath": "/gce",
"name": "gce"
}
]
}
],
"volumes": [
{
"name": "cache-volume",
"emptyDir": {}
},
{
"name": "gce",
"gcePersistentDisk": {}
}
]
}
}`
)

View file

@ -106,6 +106,14 @@ func (c *PolicyContext) Operation() kyvernov1.AdmissionOperation {
return c.operation
}
func (c *PolicyContext) SetOperation(op kyvernov1.AdmissionOperation) error {
c.operation = op
if err := c.jsonContext.AddOperation(string(op)); err != nil {
return errors.Wrapf(err, "failed to replace old object in the JSON context")
}
return nil
}
func (c *PolicyContext) NamespaceLabels() map[string]string {
return c.namespaceLabels
}

View file

@ -110,5 +110,5 @@ func IsSameRuleResponse(r1 *engineapi.RuleResponse, r2 *engineapi.RuleResponse)
func IsUpdateRequest(ctx engineapi.PolicyContext) bool {
// is the OldObject and NewObject are available, the request is an UPDATE
return ctx.OldResource().Object != nil && ctx.NewResource().Object != nil
return (ctx.OldResource().Object != nil && ctx.NewResource().Object != nil) || ctx.Operation() == kyvernov1.Update
}

View file

@ -0,0 +1,11 @@
## Description
This test ensures that request.oldObject is not null on UPDATE operations when there are multiple rules in a policy.
## Expected Behavior
The namespace update operation is allowed.
## Reference Issue(s)
https://github.com/kyverno/kyverno/issues/9885

View file

@ -0,0 +1,34 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: check-old-object
spec:
steps:
- name: step-01
try:
- create:
file: ns.yaml
- assert:
file: ns-ready.yaml
- name: step-02
try:
- create:
file: policy.yaml
- assert:
file: policy-ready.yaml
- name: step-03
try:
- update:
file: ns-update.yaml
- name: step-04
try:
- update:
file: ns-update-good.yaml
- name: step-05
try:
- update:
file: ns-update-bad.yaml
expect:
- check:
($error != null): true

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: test
labels:
kubernetes.io/metadata.name: test
size: unknown

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: test
labels:
kubernetes.io/metadata.name: test
size: bad

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: test
labels:
kubernetes.io/metadata.name: test
size: large

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: test
labels:
kubernetes.io/metadata.name: test
size: extralarge

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: test
labels:
kubernetes.io/metadata.name: test
size: unknown

View file

@ -0,0 +1,4 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-old-object

View file

@ -0,0 +1,36 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-old-object
spec:
background: false
rules:
- name: require-labels
match:
all:
- resources:
operations:
- CREATE
- UPDATE
kinds:
- Namespace
context:
- name: small
variable:
value: small
- name: medium
variable:
value: medium
- name: large
variable:
value: large
validate:
failureAction: Enforce
message: "The label `size` is required"
assert:
object:
metadata:
labels:
size:
(@ == $small || @ == $medium || @ == $large): true

View file

@ -128,7 +128,7 @@
"^validate$/^clusterpolicy$/^standard$/^cel$/^parameter-resources$/^namespaced$/^(match-clusterscoped-resource|set-paramref-namespace|unset-paramref-namespace)\\[.*\\]$",
"^validate$/^clusterpolicy$/^standard$/^debug$/^(with-pod|with-subresource|with-wildcard)\\[.*\\]$",
"^validate$/^clusterpolicy$/^standard$/^debug-deprecated$/^(with-pod|with-subresource|with-wildcard)\\[.*\\]$",
"^validate$/^clusterpolicy$/^standard$/^enforce$/^(api-initiated-pod-eviction|block-pod-exec-requests|bypass-with-policy-exception|csr|enforce-validate-existing|failure-policy-ignore-anchor|ns-selector-with-wildcard-kind|operator-allnotin-01|operator-anyin-boolean|resource-apply-block|scaling-with-kubectl-scale)\\[.*\\]$",
"^validate$/^clusterpolicy$/^standard$/^enforce$/^(api-initiated-pod-eviction|block-pod-exec-requests|bypass-with-policy-exception|csr|enforce-validate-existing|enforce-validate-existing-allow-existing-violations|enforce-validate-existing-deny|enforce-validate-existing-pss|failure-policy-ignore-anchor|ns-selector-with-wildcard-kind|operator-allnotin-01|operator-anyin-boolean|resource-apply-block|scaling-with-kubectl-scale)\\[.*\\]$",
"^validate$/^clusterpolicy$/^standard$/^enforce-deprecated$/^(api-initiated-pod-eviction|block-pod-exec-requests|bypass-with-policy-exception|csr|enforce-validate-existing|failure-policy-ignore-anchor|ns-selector-with-wildcard-kind|operator-allnotin-01|operator-anyin-boolean|resource-apply-block|scaling-with-kubectl-scale)\\[.*\\]$",
"^validate$/^clusterpolicy$/^standard$/^exclude$/^(exclude-namespace|exclude-namespace(deprecated))\\[.*\\]$",
"^validate$/^clusterpolicy$/^standard$/^operations$/^(only-update|only-update(deprecated))\\[.*\\]$",

View file

@ -15,4 +15,5 @@ spec:
kinds:
- Pod
validate:
allowExistingViolations: false
deny: {}

View file

@ -14,5 +14,6 @@ spec:
kinds:
- Pod
validate:
allowExistingViolations: false
failureAction: Enforce
deny: {}

View file

@ -0,0 +1,13 @@
## Description
This test mainly verifies that an enforce validate policy blocks changes in old objects that were present before policy was created when `allowExistingViolations` is set to `false`
## Expected Behavior
1. A bad pod is created that violates the policy.
2. The policy is applied.
3. Violating changes in bad pod causes error becuase `allowExistingViolations` is set to `false`
## Reference Issue(s)
10084

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: badpod-allow-existing
namespace: default

View file

@ -0,0 +1,8 @@
if kubectl label po badpod-allow-existing foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
then
echo "Test succeed, updating violating preexisting resource does throw error"
exit 0
else
echo "Test failed, updating violating preexisting resource should throw error"
exit 1
fi

View file

@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: badpod-allow-existing
namespace: default
labels:
foo: bad
spec:
containers:
- name: container01
image: busybox:1.35
args:
- sleep
- 1d

View file

@ -0,0 +1,25 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: enforce-validate-existing
spec:
steps:
- name: step-01
try:
- apply:
file: bad-pod.yaml
- assert:
file: bad-pod-ready.yaml
- name: step-02
try:
- apply:
file: policy.yaml
- assert:
file: policy-ready.yaml
- name: step-03
try:
- script:
content: ./bad-pod-update-test.sh
timeout: 30s

View file

@ -0,0 +1,4 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-labels-allow-existing

View file

@ -0,0 +1,22 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-labels-allow-existing
spec:
background: true
rules:
- name: check-labels
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
allowExistingViolations: false
deny:
conditions:
any:
- key: "{{ request.object.metadata.labels.foo || '' }}"
operator: NotEquals
value: 'bar'

View file

@ -0,0 +1,15 @@
## Description
This test mainly verifies that an enforce validate policy does not block changes in old objects that were present before policy was created
## Expected Behavior
1. A pod is created that violates the policy.
2. The policy is applied.
3. A pod is created that follows the policy.
4. Violating changes on bad pad does not cause error.
5. Violating changes in good pod causes error.
6. The bad pod once passed the policy, will be tracked by the policy and return error on bad changes.
## Reference Issue(s)
8837

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: badpod-deny
namespace: default

View file

@ -0,0 +1,8 @@
if kubectl label po badpod-deny foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
then
echo "Test failed, updating violating preexisting resource should not throw error"
exit 1
else
echo "Test succeed, updating violating preexisting resource does not throw error"
exit 0
fi

View file

@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: badpod-deny
namespace: default
labels:
foo: bad
spec:
containers:
- name: container01
image: busybox:1.35
args:
- sleep
- 1d

View file

@ -0,0 +1,40 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: enforce-validate-existing
spec:
steps:
- name: step-01
try:
- apply:
file: bad-pod.yaml
- assert:
file: bad-pod-ready.yaml
- name: step-02
try:
- apply:
file: policy.yaml
- assert:
file: policy-ready.yaml
- name: step-03
try:
- apply:
file: good-pod.yaml
- assert:
file: good-pod-ready.yaml
- name: step-04
try:
- script:
content: ./bad-pod-update-test.sh
timeout: 30s
- name: step-05
try:
- script:
content: ./good-pod-update-test.sh
timeout: 30s
- name: step-06
try:
- script:
content: ./update-bad-pod-to-comply.sh
timeout: 30s

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: goodpod-deny
namespace: default

View file

@ -0,0 +1,8 @@
if kubectl label po goodpod-deny foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
then
echo "Test succeed, updating violating resource throws error"
exit 0
else
echo "Test failed, updating violating resource did not throw error"
exit 1
fi

View file

@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: goodpod-deny
namespace: default
labels:
foo: bar
spec:
containers:
- name: container01
image: busybox:1.35
args:
- sleep
- 1d

View file

@ -0,0 +1,4 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-labels-deny

View file

@ -0,0 +1,21 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-labels-deny
spec:
background: true
rules:
- name: check-labels
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
deny:
conditions:
any:
- key: "{{ request.object.metadata.labels.foo || '' }}"
operator: NotEquals
value: 'bar'

View file

@ -0,0 +1,9 @@
kubectl label po badpod-deny foo=bar --overwrite
if kubectl label po badpod-deny foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
then
echo "Test succeed, updating violating resource throws error"
exit 0
else
echo "Test failed, updating violating resource did not throw error"
exit 1
fi

View file

@ -0,0 +1,17 @@
## Description
This test mainly verifies that an pss validate policy does not block changes in old objects that were present before policy was created
## Expected Behavior
1. A pod is created that violates the policy.
2. The policy is applied.
3. The bad pod is updated with a bad change, it is applied
4. The bad pod is made to comply
5. A bad change in that pod does not go through
6. A good pod is created
7. Violating changes in good pod causes error.
## Reference Issue(s)
8837

View file

@ -0,0 +1,6 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad-deployment
labels:
app: nginx

View file

@ -0,0 +1,41 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
hostNetwork: false
containers:
- name: nginx
image: ghcr.io/kyverno/test-nginx:dontpull
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

View file

@ -0,0 +1,40 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
containers:
- name: nginx
image: ghcr.io/kyverno/test-nginx:dontpull
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: true
runAsNonRoot: true
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

View file

@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1
containers:
- name: nginx
image: ghcr.io/kyverno/test-nginx:dontpull-new-image
securityContext:
allowPrivilegeEscalation: true
runAsNonRoot: true
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

View file

@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bad-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
containers:
- name: nginx
image: ghcr.io/kyverno/test-nginx:dontpull
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: true
runAsNonRoot: true
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

View file

@ -0,0 +1,45 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: enforce-validate-existing
spec:
steps:
- name: step-01
try:
- apply:
file: bad-deploy.yaml
- assert:
file: bad-deploy-ready.yaml
- name: step-02
try:
- apply:
file: policy.yaml
- assert:
file: policy-ready.yaml
- name: step-03
try:
- apply:
file: bad-deploy-update.yaml
- name: step-04
try:
- apply:
file: bad-deploy-update-comply.yaml
- name: step-05
try:
- apply:
file: bad-deploy-update-remove-comply.yaml
expect:
- check:
($error != `null`): true
- name: step-06
try:
- apply:
file: good-deploy.yaml
- name: step-07
try:
- apply:
file: good-deploy-update.yaml
expect:
- check:
($error != `null`): true

View file

@ -0,0 +1,6 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: good-deployment
labels:
app: nginx

View file

@ -0,0 +1,40 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: good-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
hostNetwork: false
containers:
- name: nginx
image: ghcr.io/kyverno/test-nginx:dontpull
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: true
runAsNonRoot: true
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

View file

@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: good-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
hostNetwork: false
containers:
- name: nginx
image: ghcr.io/kyverno/test-nginx:dontpull
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
seccompProfile:
type: Localhost
localhostProfile: operator/default/profile1.json
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

View file

@ -0,0 +1,4 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: podsecurity-subrule-baseline

View file

@ -0,0 +1,19 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: podsecurity-subrule-baseline
spec:
background: true
validationFailureAction: Enforce
rules:
- name: baseline
match:
any:
- resources:
kinds:
- Pod
- Deployment
validate:
podSecurity:
level: restricted
version: latest

View file

@ -1,5 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: badpod
name: badpod-validate-existing
namespace: default

View file

@ -1,4 +1,4 @@
if kubectl label po badpod foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
if kubectl label po badpod-validate-existing foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
then
echo "Test failed, updating violating preexisting resource should not throw error"
exit 1

View file

@ -1,7 +1,7 @@
apiVersion: v1
kind: Pod
metadata:
name: badpod
name: badpod-validate-existing
namespace: default
labels:
foo: bad

View file

@ -1,5 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: goodpod
name: goodpod-validate-existing
namespace: default

View file

@ -1,4 +1,4 @@
if kubectl label po goodpod foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
if kubectl label po goodpod-validate-existing foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
then
echo "Test succeed, updating violating resource throws error"
exit 0

View file

@ -1,7 +1,7 @@
apiVersion: v1
kind: Pod
metadata:
name: goodpod
name: goodpod-validate-existing
namespace: default
labels:
foo: bar

View file

@ -1,4 +1,4 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-labels
name: check-labels-validate-existing

View file

@ -1,7 +1,7 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-labels
name: check-labels-validate-existing
spec:
background: true
rules:

View file

@ -1,5 +1,5 @@
kubectl label po badpod foo=bar --overwrite
if kubectl label po badpod foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
kubectl label po badpod-validate-existing foo=bar --overwrite
if kubectl label po badpod-validate-existing foo=bad1 --overwrite 2>&1 | grep -q "validation error: rule check-labels"
then
echo "Test succeed, updating violating resource throws error"
exit 0