diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 3658e7da6e..9697874ed4 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -43,6 +43,7 @@ import ( "github.com/kyverno/kyverno/pkg/toggle" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime" + "github.com/kyverno/kyverno/pkg/validation/exception" "github.com/kyverno/kyverno/pkg/webhooks" webhooksexception "github.com/kyverno/kyverno/pkg/webhooks/exception" webhookspolicy "github.com/kyverno/kyverno/pkg/webhooks/policy" @@ -532,7 +533,10 @@ func main() { openApiManager, admissionReports, ) - exceptionHandlers := webhooksexception.NewHandlers() + exceptionHandlers := webhooksexception.NewHandlers(exception.ValidationOptions{ + Enabled: enablePolicyException, + Namespace: exceptionNamespace, + }) server := webhooks.NewServer( policyHandlers, resourceHandlers, diff --git a/pkg/validation/exception/validate.go b/pkg/validation/exception/validate.go index 374093e6ea..00f02b82c2 100644 --- a/pkg/validation/exception/validate.go +++ b/pkg/validation/exception/validate.go @@ -7,8 +7,24 @@ import ( kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1" ) -// Validate checks policy exception is valid -func Validate(ctx context.Context, logger logr.Logger, polex *kyvernov2alpha1.PolicyException) error { - errs := polex.Validate() - return errs.ToAggregate() +const ( + namespacesDontMatch = "PolicyException resource namespace must match the defined namespace." + disabledPolex = "PolicyException resources would not be processed until it is enabled." +) + +type ValidationOptions struct { + Enabled bool + Namespace string +} + +// Validate checks policy exception is valid +func Validate(ctx context.Context, logger logr.Logger, polex *kyvernov2alpha1.PolicyException, opts ValidationOptions) ([]string, error) { + var warnings []string + if !opts.Enabled { + warnings = append(warnings, disabledPolex) + } else if opts.Namespace != "" && opts.Namespace != polex.Namespace { + warnings = append(warnings, namespacesDontMatch) + } + errs := polex.Validate() + return warnings, errs.ToAggregate() } diff --git a/pkg/validation/exception/validate_test.go b/pkg/validation/exception/validate_test.go new file mode 100644 index 0000000000..fc2ecffe04 --- /dev/null +++ b/pkg/validation/exception/validate_test.go @@ -0,0 +1,76 @@ +package exception + +import ( + "context" + "testing" + + "github.com/kyverno/kyverno/pkg/logging" + admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" + "gotest.tools/assert" +) + +func Test_Validate(t *testing.T) { + type args struct { + opts ValidationOptions + resource []byte + } + tc := []struct { + name string + args args + want int + }{ + { + name: "PolicyExceptions disabled.", + args: args{ + opts: ValidationOptions{ + Enabled: false, + Namespace: "kyverno", + }, + resource: []byte(`{"apiVersion":"kyverno.io/v2alpha1","kind":"PolicyException","metadata":{"name":"enforce-label-exception","namespace":"delta"},"spec":{"exceptions":[{"policyName":"enforce-label","ruleNames":["enforce-label"]}],"match":{"any":[{"resources":{"kinds":["Pod"]}}]}}}`), + }, + want: 1, + }, + { + name: "PolicyExceptions enabled. Defined namespace doesn't match namespace passed.", + args: args{ + opts: ValidationOptions{ + Enabled: true, + Namespace: "kyverno", + }, + resource: []byte(`{"apiVersion":"kyverno.io/v2alpha1","kind":"PolicyException","metadata":{"name":"enforce-label-exception","namespace":"delta"},"spec":{"exceptions":[{"policyName":"enforce-label","ruleNames":["enforce-label"]}],"match":{"any":[{"resources":{"kinds":["Pod"]}}]}}}`), + }, + want: 1, + }, + { + name: "PolicyExceptions enabled. Defined namespace matches namespace passed", + args: args{ + opts: ValidationOptions{ + Enabled: true, + Namespace: "kyverno", + }, + resource: []byte(`{"apiVersion":"kyverno.io/v2alpha1","kind":"PolicyException","metadata":{"name":"enforce-label-exception","namespace":"kyverno"},"spec":{"exceptions":[{"policyName":"enforce-label","ruleNames":["enforce-label"]}],"match":{"any":[{"resources":{"kinds":["Pod"]}}]}}}`), + }, + want: 0, + }, + { + name: "PolicyExceptions enabled. No namespace defined", + args: args{ + opts: ValidationOptions{ + Enabled: true, + Namespace: "", + }, + resource: []byte(`{"apiVersion":"kyverno.io/v2alpha1","kind":"PolicyException","metadata":{"name":"enforce-label-exception","namespace":"kyverno"},"spec":{"exceptions":[{"policyName":"enforce-label","ruleNames":["enforce-label"]}],"match":{"any":[{"resources":{"kinds":["Pod"]}}]}}}`), + }, + want: 0, + }, + } + for _, c := range tc { + t.Run(c.name, func(t *testing.T) { + polex, err := admissionutils.UnmarshalPolicyException(c.args.resource) + assert.NilError(t, err) + warnings, err := Validate(context.Background(), logging.GlobalLogger(), polex, c.args.opts) + assert.NilError(t, err) + assert.Assert(t, len(warnings) == c.want) + }) + } +} diff --git a/pkg/webhooks/exception/validate.go b/pkg/webhooks/exception/validate.go index 98a4b66e5b..65179c74e5 100644 --- a/pkg/webhooks/exception/validate.go +++ b/pkg/webhooks/exception/validate.go @@ -11,10 +11,14 @@ import ( admissionv1 "k8s.io/api/admission/v1" ) -type handlers struct{} +type handlers struct { + validationOptions validation.ValidationOptions +} -func NewHandlers() webhooks.ExceptionHandlers { - return &handlers{} +func NewHandlers(validationOptions validation.ValidationOptions) webhooks.ExceptionHandlers { + return &handlers{ + validationOptions: validationOptions, + } } // Validate performs the validation check on policy exception resources @@ -24,9 +28,9 @@ func (h *handlers) Validate(ctx context.Context, logger logr.Logger, request *ad logger.Error(err, "failed to unmarshal policy exceptions from admission request") return admissionutils.Response(request.UID, err) } - if err := validation.Validate(ctx, logger, polex); err != nil { + warnings, err := validation.Validate(ctx, logger, polex, h.validationOptions) + if err != nil { logger.Error(err, "policy exception validation errors") - return admissionutils.Response(request.UID, err) } - return nil + return admissionutils.Response(request.UID, err, warnings...) }