mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-29 02:45:06 +00:00
Namespace Specific ValidationFailureAction (#2794)
* Implement ValidationFailureActionOverride Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Update CRDs Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Update getEnforceFailureErrorMsg() Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Allow validate policies to be checked Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Fix linting issues Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Added tests for ValidationFailureActionOverrides Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Added schema validation Signed-off-by: Kumar Mallikarjuna <kumarmallikarjuna1@gmail.com> * Added description for ValidationFailureActionOverrides Signed-off-by: Kumar Mallikarjuna <kumar@nirmata.com> * Policy validation Signed-off-by: Kumar Mallikarjuna <kumar@nirmata.com> * Update CRDs Signed-off-by: Kumar Mallikarjuna <kumar@nirmata.com> * Replace literals with constants Signed-off-by: Kumar Mallikarjuna <kumar@nirmata.com> * Updated Policy Cache Signed-off-by: Kumar Mallikarjuna <kumar@nirmata.com> * Refactor Signed-off-by: Kumar Mallikarjuna <kumar@nirmata.com> Co-authored-by: shuting <shutting06@gmail.com>
This commit is contained in:
parent
4124e0f682
commit
5ad0d15240
14 changed files with 904 additions and 3 deletions
|
@ -55,8 +55,14 @@ type Spec struct {
|
|||
// the admission review request (enforce), or allow (audit) the admission review request
|
||||
// and report an error in a policy report. Optional. The default value is "audit".
|
||||
// +optional
|
||||
// +kubebuilder:validation:Enum=audit;enforce
|
||||
ValidationFailureAction string `json:"validationFailureAction,omitempty" yaml:"validationFailureAction,omitempty"`
|
||||
|
||||
// ValidationFailureActionOverrides is a Cluter Policy attribute that specifies ValidationFailureAction
|
||||
// namespace-wise. It overrides ValidationFailureAction for the specified namespaces.
|
||||
// +optional
|
||||
ValidationFailureActionOverrides []ValidationFailureActionOverride `json:"validationFailureActionOverrides,omitempty" yaml:"validationFailureActionOverrides,omitempty"`
|
||||
|
||||
// Background controls if rules are applied to existing resources during a background scan.
|
||||
// Optional. Default value is "true". The value must be set to "false" if the policy rule
|
||||
// uses variables that are only available in the admission review request (e.g. user name).
|
||||
|
@ -641,3 +647,9 @@ type ResourceSpec struct {
|
|||
// Name specifies the resource name.
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
}
|
||||
|
||||
type ValidationFailureActionOverride struct {
|
||||
// +kubebuilder:validation:Enum=audit;enforce
|
||||
Action string `json:"action,omitempty" yaml:"action,omitempty"`
|
||||
Namespaces []string `json:"namespaces,omitempty" yaml:"namespaces,omitempty"`
|
||||
}
|
||||
|
|
|
@ -1330,7 +1330,25 @@ spec:
|
|||
type: boolean
|
||||
validationFailureAction:
|
||||
description: ValidationFailureAction controls if a validation policy rule failure should disallow the admission review request (enforce), or allow (audit) the admission review request and report an error in a policy report. Optional. The default value is "audit".
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
validationFailureActionOverrides:
|
||||
description: ValidationFailureActionOverrides is a Cluter Policy attribute that specifies ValidationFailureAction namespace-wise. It overrides ValidationFailureAction for the specified namespaces.
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
webhookTimeoutSeconds:
|
||||
description: WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy. After the configured time expires, the admission request may fail, or may simply ignore the policy results, based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds.
|
||||
format: int32
|
||||
|
@ -3858,7 +3876,25 @@ spec:
|
|||
type: boolean
|
||||
validationFailureAction:
|
||||
description: ValidationFailureAction controls if a validation policy rule failure should disallow the admission review request (enforce), or allow (audit) the admission review request and report an error in a policy report. Optional. The default value is "audit".
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
validationFailureActionOverrides:
|
||||
description: ValidationFailureActionOverrides is a Cluter Policy attribute that specifies ValidationFailureAction namespace-wise. It overrides ValidationFailureAction for the specified namespaces.
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
webhookTimeoutSeconds:
|
||||
description: WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy. After the configured time expires, the admission request may fail, or may simply ignore the policy results, based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds.
|
||||
format: int32
|
||||
|
|
|
@ -2121,7 +2121,27 @@ spec:
|
|||
rule failure should disallow the admission review request (enforce),
|
||||
or allow (audit) the admission review request and report an error
|
||||
in a policy report. Optional. The default value is "audit".
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
validationFailureActionOverrides:
|
||||
description: ValidationFailureActionOverrides is a Cluter Policy attribute
|
||||
that specifies ValidationFailureAction namespace-wise. It overrides
|
||||
ValidationFailureAction for the specified namespaces.
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
webhookTimeoutSeconds:
|
||||
description: WebhookTimeoutSeconds specifies the maximum time in seconds
|
||||
allowed to apply this policy. After the configured time expires,
|
||||
|
|
|
@ -2122,7 +2122,27 @@ spec:
|
|||
rule failure should disallow the admission review request (enforce),
|
||||
or allow (audit) the admission review request and report an error
|
||||
in a policy report. Optional. The default value is "audit".
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
validationFailureActionOverrides:
|
||||
description: ValidationFailureActionOverrides is a Cluter Policy attribute
|
||||
that specifies ValidationFailureAction namespace-wise. It overrides
|
||||
ValidationFailureAction for the specified namespaces.
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
webhookTimeoutSeconds:
|
||||
description: WebhookTimeoutSeconds specifies the maximum time in seconds
|
||||
allowed to apply this policy. After the configured time expires,
|
||||
|
|
|
@ -2137,7 +2137,27 @@ spec:
|
|||
rule failure should disallow the admission review request (enforce),
|
||||
or allow (audit) the admission review request and report an error
|
||||
in a policy report. Optional. The default value is "audit".
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
validationFailureActionOverrides:
|
||||
description: ValidationFailureActionOverrides is a Cluter Policy attribute
|
||||
that specifies ValidationFailureAction namespace-wise. It overrides
|
||||
ValidationFailureAction for the specified namespaces.
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
webhookTimeoutSeconds:
|
||||
description: WebhookTimeoutSeconds specifies the maximum time in seconds
|
||||
allowed to apply this policy. After the configured time expires,
|
||||
|
@ -5856,7 +5876,27 @@ spec:
|
|||
rule failure should disallow the admission review request (enforce),
|
||||
or allow (audit) the admission review request and report an error
|
||||
in a policy report. Optional. The default value is "audit".
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
validationFailureActionOverrides:
|
||||
description: ValidationFailureActionOverrides is a Cluter Policy attribute
|
||||
that specifies ValidationFailureAction namespace-wise. It overrides
|
||||
ValidationFailureAction for the specified namespaces.
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
webhookTimeoutSeconds:
|
||||
description: WebhookTimeoutSeconds specifies the maximum time in seconds
|
||||
allowed to apply this policy. After the configured time expires,
|
||||
|
|
|
@ -2126,7 +2126,27 @@ spec:
|
|||
rule failure should disallow the admission review request (enforce),
|
||||
or allow (audit) the admission review request and report an error
|
||||
in a policy report. Optional. The default value is "audit".
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
validationFailureActionOverrides:
|
||||
description: ValidationFailureActionOverrides is a Cluter Policy attribute
|
||||
that specifies ValidationFailureAction namespace-wise. It overrides
|
||||
ValidationFailureAction for the specified namespaces.
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
webhookTimeoutSeconds:
|
||||
description: WebhookTimeoutSeconds specifies the maximum time in seconds
|
||||
allowed to apply this policy. After the configured time expires,
|
||||
|
@ -5821,7 +5841,27 @@ spec:
|
|||
rule failure should disallow the admission review request (enforce),
|
||||
or allow (audit) the admission review request and report an error
|
||||
in a policy report. Optional. The default value is "audit".
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
validationFailureActionOverrides:
|
||||
description: ValidationFailureActionOverrides is a Cluter Policy attribute
|
||||
that specifies ValidationFailureAction namespace-wise. It overrides
|
||||
ValidationFailureAction for the specified namespaces.
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
enum:
|
||||
- audit
|
||||
- enforce
|
||||
type: string
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
webhookTimeoutSeconds:
|
||||
description: WebhookTimeoutSeconds specifies the maximum time in seconds
|
||||
allowed to apply this policy. After the configured time expires,
|
||||
|
|
|
@ -32,6 +32,8 @@ type PolicyResponse struct {
|
|||
Rules []RuleResponse `json:"rules"`
|
||||
// ValidationFailureAction: audit (default) or enforce
|
||||
ValidationFailureAction string
|
||||
|
||||
ValidationFailureActionOverrides []ValidationFailureActionOverride
|
||||
}
|
||||
|
||||
//PolicySpec policy
|
||||
|
@ -173,3 +175,8 @@ func (er EngineResponse) getRules(status RuleStatus) []string {
|
|||
|
||||
return rules
|
||||
}
|
||||
|
||||
type ValidationFailureActionOverride struct {
|
||||
Action string `json:"action"`
|
||||
Namespaces []string `json:"namespaces"`
|
||||
}
|
||||
|
|
|
@ -72,6 +72,11 @@ func buildResponse(ctx *PolicyContext, resp *response.EngineResponse, startTime
|
|||
resp.PolicyResponse.Resource.Kind = resp.PatchedResource.GetKind()
|
||||
resp.PolicyResponse.Resource.APIVersion = resp.PatchedResource.GetAPIVersion()
|
||||
resp.PolicyResponse.ValidationFailureAction = ctx.Policy.Spec.ValidationFailureAction
|
||||
|
||||
for _, v := range ctx.Policy.Spec.ValidationFailureActionOverrides {
|
||||
resp.PolicyResponse.ValidationFailureActionOverrides = append(resp.PolicyResponse.ValidationFailureActionOverrides, response.ValidationFailureActionOverride{Action: v.Action, Namespaces: v.Namespaces})
|
||||
}
|
||||
|
||||
resp.PolicyResponse.ProcessingTime = time.Since(startTime)
|
||||
resp.PolicyResponse.PolicyExecutionTimestamp = startTime.Unix()
|
||||
}
|
||||
|
|
|
@ -106,6 +106,10 @@ func Validate(policy *kyverno.ClusterPolicy, client *dclient.Client, mock bool,
|
|||
clusterResourcesMap := make(map[string]*struct{})
|
||||
// Get all the cluster type kind supported by cluster
|
||||
|
||||
if len(policy.Spec.ValidationFailureActionOverrides) > 0 {
|
||||
return fmt.Errorf("invalid policy: use of ValidationFailureActionOverrides in a Namespace Policy")
|
||||
}
|
||||
|
||||
res, err := client.DiscoveryClient.DiscoveryCache().ServerPreferredResources()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -104,7 +104,14 @@ func (m *pMap) add(policy *kyverno.ClusterPolicy) {
|
|||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
enforcePolicy := policy.Spec.ValidationFailureAction == "enforce"
|
||||
enforcePolicy := policy.Spec.ValidationFailureAction == common.Enforce
|
||||
for _, k := range policy.Spec.ValidationFailureActionOverrides {
|
||||
if k.Action == common.Enforce {
|
||||
enforcePolicy = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
mutateMap := m.nameCacheMap[Mutate]
|
||||
validateEnforceMap := m.nameCacheMap[ValidateEnforce]
|
||||
validateAuditMap := m.nameCacheMap[ValidateAudit]
|
||||
|
|
|
@ -816,6 +816,114 @@ func newNsMutatePolicy(t *testing.T) *kyverno.ClusterPolicy {
|
|||
return convertPolicyToClusterPolicy(policy)
|
||||
}
|
||||
|
||||
func newValidateAuditPolicy(t *testing.T) *kyverno.ClusterPolicy {
|
||||
rawPolicy := []byte(`{
|
||||
"metadata": {
|
||||
"name": "check-label-app-audit"
|
||||
},
|
||||
"spec": {
|
||||
"background": false,
|
||||
"rules": [
|
||||
{
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "check-label-app",
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationFailureAction": "audit",
|
||||
"validationFailureActionOverrides": [
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy *kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
return policy
|
||||
}
|
||||
|
||||
func newValidateEnforcePolicy(t *testing.T) *kyverno.ClusterPolicy {
|
||||
rawPolicy := []byte(`{
|
||||
"metadata": {
|
||||
"name": "check-label-app-enforce"
|
||||
},
|
||||
"spec": {
|
||||
"background": false,
|
||||
"rules": [
|
||||
{
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "check-label-app",
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationFailureAction": "enforce",
|
||||
"validationFailureActionOverrides": [
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy *kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
return policy
|
||||
}
|
||||
|
||||
func Test_Ns_All(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newNsPolicy(t)
|
||||
|
@ -1045,3 +1153,34 @@ func Test_NsMutate_Policy(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_Validate_Enforce_Policy(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy1 := newValidateAuditPolicy(t)
|
||||
policy2 := newValidateEnforcePolicy(t)
|
||||
pCache.Add(policy1)
|
||||
pCache.Add(policy2)
|
||||
|
||||
validateEnforce := pCache.get(ValidateEnforce, "Pod", "")
|
||||
if len(validateEnforce) != 2 {
|
||||
t.Errorf("adding: expected 2 validate enforce policy, found %v", len(validateEnforce))
|
||||
}
|
||||
|
||||
validateAudit := pCache.get(ValidateAudit, "Pod", "")
|
||||
if len(validateAudit) != 0 {
|
||||
t.Errorf("adding: expected 0 validate audit policy, found %v", len(validateAudit))
|
||||
}
|
||||
|
||||
pCache.Remove(policy1)
|
||||
pCache.Remove(policy2)
|
||||
|
||||
validateEnforce = pCache.get(ValidateEnforce, "Pod", "")
|
||||
if len(validateEnforce) != 0 {
|
||||
t.Errorf("removing: expected 0 validate enforce policy, found %v", len(validateEnforce))
|
||||
}
|
||||
|
||||
validateAudit = pCache.get(ValidateAudit, "Pod", "")
|
||||
if len(validateAudit) != 0 {
|
||||
t.Errorf("removing: expected 0 validate audit policy, found %v", len(validateAudit))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/common"
|
||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"github.com/minio/pkg/wildcard"
|
||||
yamlv2 "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
@ -25,11 +26,37 @@ func isResponseSuccessful(engineReponses []*response.EngineResponse) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func checkEngineResponse(er *response.EngineResponse) bool {
|
||||
nsAction := ""
|
||||
actionOverride := false
|
||||
|
||||
for _, v := range er.PolicyResponse.ValidationFailureActionOverrides {
|
||||
action := v.Action
|
||||
if action != common.Enforce && action != common.Audit {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ns := range v.Namespaces {
|
||||
if wildcard.Match(ns, er.PatchedResource.GetNamespace()) {
|
||||
nsAction = action
|
||||
actionOverride = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if actionOverride {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return !er.IsSuccessful() && ((actionOverride && nsAction == common.Enforce) || (!actionOverride && er.PolicyResponse.ValidationFailureAction == common.Enforce))
|
||||
}
|
||||
|
||||
// returns true -> if there is even one policy that blocks resource request
|
||||
// returns false -> if all the policies are meant to report only, we dont block resource request
|
||||
func toBlockResource(engineReponses []*response.EngineResponse, log logr.Logger) bool {
|
||||
for _, er := range engineReponses {
|
||||
if !er.IsSuccessful() && er.PolicyResponse.ValidationFailureAction == common.Enforce {
|
||||
if checkEngineResponse(er) {
|
||||
log.Info("spec.ValidationFailureAction set to enforce blocking resource request", "policy", er.PolicyResponse.Policy.Name)
|
||||
return true
|
||||
}
|
||||
|
@ -44,7 +71,7 @@ func getEnforceFailureErrorMsg(engineResponses []*response.EngineResponse) strin
|
|||
policyToRule := make(map[string]interface{})
|
||||
var resourceName string
|
||||
for _, er := range engineResponses {
|
||||
if !er.IsSuccessful() && er.PolicyResponse.ValidationFailureAction == common.Enforce {
|
||||
if checkEngineResponse(er) {
|
||||
ruleToReason := make(map[string]string)
|
||||
for _, rule := range er.PolicyResponse.Rules {
|
||||
if rule.Status != response.RuleStatusPass {
|
||||
|
|
|
@ -100,6 +100,7 @@ func (v *validationHandler) handleValidation(
|
|||
// create an event on the resource
|
||||
events := generateEvents(engineResponses, blocked, (request.Operation == v1beta1.Update), logger)
|
||||
v.eventGen.Add(events...)
|
||||
|
||||
if blocked {
|
||||
logger.V(4).Info("resource blocked")
|
||||
//registering the kyverno_admission_review_duration_seconds metric concurrently
|
||||
|
|
543
pkg/webhooks/validation_test.go
Normal file
543
pkg/webhooks/validation_test.go
Normal file
|
@ -0,0 +1,543 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
log "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||
"github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestValidate_failure_action_overrides(t *testing.T) {
|
||||
|
||||
testcases := []struct {
|
||||
rawPolicy []byte
|
||||
rawResource []byte
|
||||
blocked bool
|
||||
}{
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "check-label-app"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "audit",
|
||||
"validationFailureActionOverrides":
|
||||
[
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"name": "check-label-app",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
rawResource: []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "test-pod",
|
||||
"namespace": "default"
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
blocked: true,
|
||||
},
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "check-label-app"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "audit",
|
||||
"validationFailureActionOverrides":
|
||||
[
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"name": "check-label-app",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
rawResource: []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "test-pod",
|
||||
"labels": {
|
||||
"app": "my-app"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
blocked: false,
|
||||
},
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "check-label-app"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "audit",
|
||||
"validationFailureActionOverrides":
|
||||
[
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"name": "check-label-app",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
rawResource: []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "test-pod",
|
||||
"namespace": "test"
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
blocked: false,
|
||||
},
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "check-label-app"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "enforce",
|
||||
"validationFailureActionOverrides":
|
||||
[
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"name": "check-label-app",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
rawResource: []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "test-pod",
|
||||
"namespace": "default"
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
blocked: true,
|
||||
},
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "check-label-app"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "enforce",
|
||||
"validationFailureActionOverrides":
|
||||
[
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"name": "check-label-app",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
rawResource: []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "test-pod",
|
||||
"labels": {
|
||||
"app": "my-app"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
blocked: false,
|
||||
},
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "check-label-app"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "enforce",
|
||||
"validationFailureActionOverrides":
|
||||
[
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"name": "check-label-app",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
rawResource: []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "test-pod",
|
||||
"namespace": "test"
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
blocked: false,
|
||||
},
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "check-label-app"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "enforce",
|
||||
"validationFailureActionOverrides":
|
||||
[
|
||||
{
|
||||
"action": "enforce",
|
||||
"namespaces": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "audit",
|
||||
"namespaces": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"name": "check-label-app",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The label 'app' is required.",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "?*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
rawResource: []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "test-pod",
|
||||
"namespace": ""
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
blocked: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testcases {
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
var policy kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(tc.rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
resourceUnstructured, err := utils.ConvertToUnstructured(tc.rawResource)
|
||||
assert.NilError(t, err)
|
||||
msgs := []string{
|
||||
"validation error: The label 'app' is required. Rule check-label-app failed at path /metadata/labels/",
|
||||
}
|
||||
|
||||
er := engine.Validate(&engine.PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
|
||||
if tc.blocked {
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
}
|
||||
}
|
||||
|
||||
blocked := toBlockResource([]*response.EngineResponse{er}, log.Log.WithName("WebhookServer"))
|
||||
assert.Assert(t, tc.blocked == blocked)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue