mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-16 20:48:42 +00:00
* fix: allow changes to preexisting resource in violation of a policy in Enforce * fix: missing error check * fix: tests * nit: cleanup * fix * fix: update old policy context * fix: preconditions always retured true internal.CheckPreconditions always returned true when v.anyAllConditions, it should be populated with rule.RawAnyAllConditions when newValidator() is used to create a validator * fix: fix chainsaw test * fix: nit * debug * feat: update test * fix: add namespace * feat: add test for bad to good conversion * feat: add test step --------- Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> Co-authored-by: Vishal Choudhary <vishal.choudhary@nirmata.com> Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
parent
52526f8425
commit
d3d0eb354f
20 changed files with 258 additions and 8 deletions
|
@ -25,6 +25,7 @@ type PolicyContext interface {
|
||||||
Element() unstructured.Unstructured
|
Element() unstructured.Unstructured
|
||||||
SetElement(element unstructured.Unstructured)
|
SetElement(element unstructured.Unstructured)
|
||||||
|
|
||||||
|
OldPolicyContext() (PolicyContext, error)
|
||||||
JSONContext() enginecontext.Interface
|
JSONContext() enginecontext.Interface
|
||||||
Copy() PolicyContext
|
Copy() PolicyContext
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/kyverno/kyverno/pkg/utils/api"
|
"github.com/kyverno/kyverno/pkg/utils/api"
|
||||||
datautils "github.com/kyverno/kyverno/pkg/utils/data"
|
datautils "github.com/kyverno/kyverno/pkg/utils/data"
|
||||||
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
|
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
@ -55,6 +56,7 @@ type validator struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newValidator(log logr.Logger, contextLoader engineapi.EngineContextLoader, ctx engineapi.PolicyContext, rule kyvernov1.Rule) *validator {
|
func newValidator(log logr.Logger, contextLoader engineapi.EngineContextLoader, ctx engineapi.PolicyContext, rule kyvernov1.Rule) *validator {
|
||||||
|
anyAllConditions, _ := datautils.ToMap(rule.RawAnyAllConditions)
|
||||||
return &validator{
|
return &validator{
|
||||||
log: log,
|
log: log,
|
||||||
rule: rule,
|
rule: rule,
|
||||||
|
@ -63,6 +65,7 @@ func newValidator(log logr.Logger, contextLoader engineapi.EngineContextLoader,
|
||||||
pattern: rule.Validation.GetPattern(),
|
pattern: rule.Validation.GetPattern(),
|
||||||
anyPattern: rule.Validation.GetAnyPattern(),
|
anyPattern: rule.Validation.GetAnyPattern(),
|
||||||
deny: rule.Validation.Deny,
|
deny: rule.Validation.Deny,
|
||||||
|
anyAllConditions: anyAllConditions,
|
||||||
forEach: rule.Validation.ForEachValidation,
|
forEach: rule.Validation.ForEachValidation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,6 +124,22 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleResponse := v.validateResourceWithRule()
|
ruleResponse := v.validateResourceWithRule()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ruleResponse
|
return ruleResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +152,20 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *validator) validateOldObject(ctx context.Context) (*engineapi.RuleResponse, error) {
|
||||||
|
pc := v.policyContext
|
||||||
|
oldPc, err := v.policyContext.OldPolicyContext()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "cannot get old policy context")
|
||||||
|
}
|
||||||
|
|
||||||
|
v.policyContext = oldPc
|
||||||
|
resp := v.validate(ctx)
|
||||||
|
v.policyContext = pc
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (v *validator) validateForEach(ctx context.Context) *engineapi.RuleResponse {
|
func (v *validator) validateForEach(ctx context.Context) *engineapi.RuleResponse {
|
||||||
applyCount := 0
|
applyCount := 0
|
||||||
for _, foreach := range v.forEach {
|
for _, foreach := range v.forEach {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
|
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/jmespath"
|
"github.com/kyverno/kyverno/pkg/engine/jmespath"
|
||||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||||
|
"github.com/pkg/errors"
|
||||||
admissionv1 "k8s.io/api/admission/v1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
@ -61,6 +62,26 @@ func (c *PolicyContext) Policy() kyvernov1.PolicyInterface {
|
||||||
return c.policy
|
return c.policy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *PolicyContext) OldPolicyContext() (engineapi.PolicyContext, error) {
|
||||||
|
if c.Operation() != kyvernov1.Update {
|
||||||
|
return nil, errors.New("cannot create old policy context")
|
||||||
|
}
|
||||||
|
copy := c.copy()
|
||||||
|
oldJsonContext := copy.jsonContext
|
||||||
|
copy.oldResource = unstructured.Unstructured{}
|
||||||
|
copy.newResource = c.oldResource
|
||||||
|
|
||||||
|
if err := oldJsonContext.AddResource(nil); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to replace object in the JSON context")
|
||||||
|
}
|
||||||
|
if err := oldJsonContext.AddOldResource(copy.OldResource().Object); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to replace old object in the JSON context")
|
||||||
|
}
|
||||||
|
|
||||||
|
copy.jsonContext = oldJsonContext
|
||||||
|
return copy, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *PolicyContext) NewResource() unstructured.Unstructured {
|
func (c *PolicyContext) NewResource() unstructured.Unstructured {
|
||||||
return c.newResource
|
return c.newResource
|
||||||
}
|
}
|
||||||
|
@ -228,6 +249,7 @@ func NewPolicyContext(
|
||||||
if admissionInfo != nil {
|
if admissionInfo != nil {
|
||||||
policyContext = policyContext.WithAdmissionInfo(*admissionInfo)
|
policyContext = policyContext.WithAdmissionInfo(*admissionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
return policyContext, nil
|
return policyContext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,6 +278,7 @@ func NewPolicyContextFromAdmissionRequest(
|
||||||
WithAdmissionOperation(true).
|
WithAdmissionOperation(true).
|
||||||
WithResourceKind(gvk, request.SubResource).
|
WithResourceKind(gvk, request.SubResource).
|
||||||
WithRequestResource(request.Resource)
|
WithRequestResource(request.Resource)
|
||||||
|
|
||||||
return policyContext, nil
|
return policyContext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,3 +85,19 @@ func TransformConditions(original apiextensions.JSON) (interface{}, error) {
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("invalid preconditions")
|
return nil, fmt.Errorf("invalid preconditions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsSameRuleResponse(r1 *engineapi.RuleResponse, r2 *engineapi.RuleResponse) bool {
|
||||||
|
if r1.Name() != r2.Name() ||
|
||||||
|
r1.RuleType() != r2.RuleType() ||
|
||||||
|
r1.Message() != r2.Message() ||
|
||||||
|
r1.Status() != r2.Status() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: TestStep
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: badpod
|
||||||
|
spec:
|
||||||
|
timeouts: {}
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: bad-pod.yaml
|
||||||
|
- assert:
|
||||||
|
file: bad-pod-ready.yaml
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: TestStep
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: policy
|
||||||
|
spec:
|
||||||
|
timeouts: {}
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: policy.yaml
|
||||||
|
- assert:
|
||||||
|
file: policy-ready.yaml
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: TestStep
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: goodpod
|
||||||
|
spec:
|
||||||
|
timeouts: {}
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
file: good-pod.yaml
|
||||||
|
- assert:
|
||||||
|
file: good-pod-ready.yaml
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: TestStep
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: badpod
|
||||||
|
spec:
|
||||||
|
timeouts: {}
|
||||||
|
try:
|
||||||
|
- script:
|
||||||
|
content: ./bad-pod-update-test.sh
|
||||||
|
timeout: 30s
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: TestStep
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: badpod
|
||||||
|
spec:
|
||||||
|
timeouts: {}
|
||||||
|
try:
|
||||||
|
- script:
|
||||||
|
content: ./good-pod-update-test.sh
|
||||||
|
timeout: 30s
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: TestStep
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: badtogoodpod
|
||||||
|
spec:
|
||||||
|
timeouts: {}
|
||||||
|
try:
|
||||||
|
- script:
|
||||||
|
content: ./update-bad-pod-to-comply.sh
|
||||||
|
timeout: 30s
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: badpod
|
||||||
|
namespace: default
|
|
@ -0,0 +1,8 @@
|
||||||
|
if kubectl label po badpod 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
|
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: badpod
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
foo: bad
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: container01
|
||||||
|
image: busybox:1.35
|
||||||
|
args:
|
||||||
|
- sleep
|
||||||
|
- 1d
|
|
@ -0,0 +1,5 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: goodpod
|
||||||
|
namespace: default
|
|
@ -0,0 +1,8 @@
|
||||||
|
if kubectl label po goodpod 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
|
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: goodpod
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
foo: bar
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: container01
|
||||||
|
image: busybox:1.35
|
||||||
|
args:
|
||||||
|
- sleep
|
||||||
|
- 1d
|
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: check-labels
|
|
@ -0,0 +1,19 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: check-labels
|
||||||
|
spec:
|
||||||
|
validationFailureAction: Enforce
|
||||||
|
background: true
|
||||||
|
rules:
|
||||||
|
- name: check-labels
|
||||||
|
match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- Pod
|
||||||
|
validate:
|
||||||
|
pattern:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
=(foo): "bar"
|
|
@ -0,0 +1,9 @@
|
||||||
|
kubectl label po badpod foo=bar --overwrite
|
||||||
|
if kubectl label po badpod 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
|
Loading…
Add table
Reference in a new issue