diff --git a/charts/kyverno/crds/crds.yaml b/charts/kyverno/crds/crds.yaml index e73ad35c65..72b50024b6 100644 --- a/charts/kyverno/crds/crds.yaml +++ b/charts/kyverno/crds/crds.yaml @@ -70,7 +70,7 @@ spec: type: string name: type: string - Namespace: + namespace: type: string resources: type: object @@ -133,7 +133,7 @@ spec: type: string name: type: string - Namespace: + namespace: type: string resources: type: object @@ -210,6 +210,28 @@ spec: AnyValue: {} anyPattern: AnyValue: {} + deny: + properties: + conditions: + type: array + items: + type: object + required: + - key # can be of any type + - operator # typed + - value # can be of any type + properties: + operator: + type: string + enum: + - Equal + - Equals + - NotEqual + - NotEquals + key: + type: string + value: + type: string generate: type: object required: diff --git a/definitions/install.yaml b/definitions/install.yaml index 001994bbfb..ae70f0426f 100644 --- a/definitions/install.yaml +++ b/definitions/install.yaml @@ -707,7 +707,7 @@ metadata: namespace: kyverno data: # resource types to be skipped by kyverno policy engine - resourceFilters: "[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]" + resourceFilters: "[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*][Binding,*,*][ReplicaSet,*,*]" --- apiVersion: apps/v1 kind: Deployment @@ -729,14 +729,16 @@ spec: serviceAccountName: kyverno-service-account initContainers: - name: kyverno-pre - image: nirmata/kyvernopre:v1.1.5 + image: nirmata/kyvernopre:v1.1.6-rc1 containers: - name: kyverno - image: nirmata/kyverno:v1.1.5 + image: registry-v2.nirmata.io/nirmata/kyverno:latest + imagePullPolicy: Always args: - "--filterK8Resources=[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*][Binding,*,*][ReplicaSet,*,*]" # customize webhook timeout #- "--webhooktimeout=4" + - "-v=6" ports: - containerPort: 443 env: diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index 0127e15b72..e2312f3090 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -2,7 +2,6 @@ package engine import ( "encoding/json" - "reflect" "strings" "time" @@ -96,15 +95,11 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse) incrementAppliedRuleCount(&resp) } - - // insert annotation to podtemplate if resource is pod controller - // skip inserting on existing resource - if reflect.DeepEqual(policyContext.AdmissionInfo, kyverno.RequestInfo{}) { - continue - } } - if strings.Contains(PodControllers, resource.GetKind()) { + pocliyAnnotation := policy.GetAnnotations() + givenPodControllers := pocliyAnnotation[PodControllersAnnotation] + if strings.Contains(givenPodControllers, resource.GetKind()) { if !patchedResourceHasPodControllerAnnotation(patchedResource) { var ruleResponse response.RuleResponse ruleResponse, patchedResource = mutate.ProcessOverlay(logger, "podControllerAnnotation", podTemplateRule.Mutation.Overlay, patchedResource) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 94f18a346b..152774c04c 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -242,7 +242,7 @@ func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstr if path, err := validate.ValidateResourceWithPattern(logger, resource.Object, pattern); err != nil { // validation failed resp.Success = false - resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed at path '%s'", + resp.Message = fmt.Sprintf("Validation error: %s; Validation rule %s failed at path %s", rule.Validation.Message, rule.Name, path) return resp } diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index c3a9ffae9a..03c3361768 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -125,7 +125,7 @@ func TestValidate_image_tag_fail(t *testing.T) { assert.NilError(t, err) msgs := []string{ "Validation rule 'validate-tag' succeeded.", - "Validation error: imagePullPolicy 'Always' required with tag 'latest'; Validation rule 'validate-latest' failed at path '/spec/containers/0/imagePullPolicy/'", + "Validation error: imagePullPolicy 'Always' required with tag 'latest'; Validation rule validate-latest failed at path /spec/containers/0/imagePullPolicy/", } er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) for index, r := range er.PolicyResponse.Rules { @@ -383,7 +383,7 @@ func TestValidate_host_network_port(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation error: Host network and port are not allowed; Validation rule 'validate-host-network-port' failed at path '/spec/containers/0/ports/0/hostPort/'"} + msgs := []string{"Validation error: Host network and port are not allowed; Validation rule validate-host-network-port failed at path /spec/containers/0/ports/0/hostPort/"} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -561,7 +561,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation error: Host path '/var/lib/' is not allowed; Validation rule 'validate-host-path' failed at path '/spec/volumes/0/hostPath/path/'"} + msgs := []string{"Validation error: Host path '/var/lib/' is not allowed; Validation rule validate-host-path failed at path /spec/volumes/0/hostPath/path/"} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -777,7 +777,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation error: pod: validate run as non root user; Validation rule 'pod rule 2' failed at path '/spec/securityContext/runAsNonRoot/'"} + msgs := []string{"Validation error: pod: validate run as non root user; Validation rule pod rule 2 failed at path /spec/securityContext/runAsNonRoot/"} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) @@ -1166,7 +1166,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured}) - msgs := []string{"Validation error: Host path is not allowed; Validation rule 'validate-host-path' failed at path '/spec/volumes/0/hostPath/'"} + msgs := []string{"Validation error: Host path is not allowed; Validation rule validate-host-path failed at path /spec/volumes/0/hostPath/"} for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) diff --git a/pkg/policy/existing.go b/pkg/policy/existing.go index 44850b4df0..75e7dfcbe4 100644 --- a/pkg/policy/existing.go +++ b/pkg/policy/existing.go @@ -35,8 +35,8 @@ func (pc *PolicyController) processExistingResources(policy kyverno.ClusterPolic // skip reporting violation on pod which has annotation pod-policies.kyverno.io/autogen-applied ann := policy.GetAnnotations() - if _, ok := ann[engine.PodTemplateAnnotation]; ok { - if ann[engine.PodTemplateAnnotation] != "none" { + if annValue, ok := ann[engine.PodControllersAnnotation]; ok { + if annValue != "none" { if skipPodApplication(resource, logger) { continue } diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index 4ad5b35e94..d3155d2b74 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -45,9 +45,6 @@ func (ws *WebhookServer) HandleMutation( policyContext.Policy = policy engineResponse := engine.Mutate(policyContext) - if engineResponse.PolicyResponse.RulesAppliedCount <= 0 { - continue - } engineResponses = append(engineResponses, engineResponse) ws.statusListener.Send(mutateStats{resp: engineResponse}) diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 4607639557..65794956bc 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -277,10 +277,12 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1 // MUTATION // mutation failure should not block the resource creation // any mutation failure is reported as the violation - patches := ws.HandleMutation(request, resource, policies, ctx, userRequestInfo) + patches = ws.HandleMutation(request, resource, policies, ctx, userRequestInfo) + logger.V(6).Info("", "generated patches", string(patches)) // patch the resource with patches before handling validation rules patchedResource = processResourceWithPatches(patches, request.Object.Raw, logger) + logger.V(6).Info("", "patchedResource", string(patchedResource)) if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" { // VALIDATION @@ -296,6 +298,8 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1 } } } + } else { + logger.Info("mutate and validate rules are not supported prior to Kubernetes 1.14.0") } // GENERATE @@ -330,6 +334,18 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1 } func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { + logger := ws.log.WithName("resourceValidation").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation) + + if ok := utils.HigherThanKubernetesVersion(ws.client, ws.log, 1, 14, 0); !ok { + logger.Info("mutate and validate rules are not supported prior to Kubernetes 1.14.0") + return &v1beta1.AdmissionResponse{ + Allowed: true, + Result: &metav1.Status{ + Status: "Success", + }, + } + } + if excludeKyvernoResources(request.Kind.Kind) { return &v1beta1.AdmissionResponse{ Allowed: true, @@ -339,7 +355,6 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) * } } - logger := ws.log.WithName("resourceValidation").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation) policies, err := ws.pMetaStore.ListAll() if err != nil { // Unable to connect to policy Lister to access policies @@ -406,18 +421,15 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) * } } - higherVersion := utils.HigherThanKubernetesVersion(ws.client, ws.log, 1, 14, 0) - if higherVersion { - ok, msg := ws.HandleValidation(request, policies, nil, ctx, userRequestInfo) - if !ok { - logger.Info("admission request denied") - return &v1beta1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Status: "Failure", - Message: msg, - }, - } + ok, msg := ws.HandleValidation(request, policies, nil, ctx, userRequestInfo) + if !ok { + logger.Info("admission request denied") + return &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: "Failure", + Message: msg, + }, } } diff --git a/test/scenarios/other/scenario_validate_disallow_default_serviceaccount.yaml b/test/scenarios/other/scenario_validate_disallow_default_serviceaccount.yaml index 9d69addcaf..8c8e82693d 100644 --- a/test/scenarios/other/scenario_validate_disallow_default_serviceaccount.yaml +++ b/test/scenarios/other/scenario_validate_disallow_default_serviceaccount.yaml @@ -14,5 +14,5 @@ expected: rules: - name: prevent-mounting-default-serviceaccount type: Validation - message: "Validation error: Prevent mounting of default service account; Validation rule 'prevent-mounting-default-serviceaccount' failed at path '/spec/serviceAccountName/'" + message: "Validation error: Prevent mounting of default service account; Validation rule prevent-mounting-default-serviceaccount failed at path /spec/serviceAccountName/" success: false \ No newline at end of file diff --git a/test/scenarios/other/scenario_validate_selinux_context.yaml b/test/scenarios/other/scenario_validate_selinux_context.yaml index 8847a64980..91d0f4e0df 100644 --- a/test/scenarios/other/scenario_validate_selinux_context.yaml +++ b/test/scenarios/other/scenario_validate_selinux_context.yaml @@ -15,5 +15,5 @@ expected: rules: - name: validate-selinux-options type: Validation - message: "Validation error: SELinux level is required; Validation rule 'validate-selinux-options' failed at path '/spec/containers/0/securityContext/seLinuxOptions/'" + message: "Validation error: SELinux level is required; Validation rule validate-selinux-options failed at path /spec/containers/0/securityContext/seLinuxOptions/" success: false \ No newline at end of file diff --git a/test/scenarios/samples/best_practices/disallow_docker_sock_mount.yaml b/test/scenarios/samples/best_practices/disallow_docker_sock_mount.yaml index a0de3a847f..acb30560e9 100644 --- a/test/scenarios/samples/best_practices/disallow_docker_sock_mount.yaml +++ b/test/scenarios/samples/best_practices/disallow_docker_sock_mount.yaml @@ -14,5 +14,5 @@ expected: rules: - name: validate-docker-sock-mount type: Validation - message: "Validation error: Use of the Docker Unix socket is not allowed; Validation rule 'validate-docker-sock-mount' failed at path '/spec/volumes/0/hostPath/path/'" + message: "Validation error: Use of the Docker Unix socket is not allowed; Validation rule validate-docker-sock-mount failed at path /spec/volumes/0/hostPath/path/" success: false \ No newline at end of file diff --git a/test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml b/test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml index ecadd7dca5..4febde8ad1 100644 --- a/test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml +++ b/test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml @@ -12,5 +12,5 @@ expected: rules: - name: validate-helm-tiller type: Validation - message: "Validation error: Helm Tiller is not allowed; Validation rule 'validate-helm-tiller' failed at path '/spec/containers/0/image/'" + message: "Validation error: Helm Tiller is not allowed; Validation rule validate-helm-tiller failed at path /spec/containers/0/image/" success: false