diff --git a/examples/policy_validate_nonRootUser.yaml b/examples/policy_validate_nonRootUser.yaml index 16bc4505ce..712d46b10a 100644 --- a/examples/policy_validate_nonRootUser.yaml +++ b/examples/policy_validate_nonRootUser.yaml @@ -1,22 +1,27 @@ -apiVersion : kyverno.io/v1alpha1 +apiVersion: kyverno.io/v1alpha1 kind: Policy metadata: - name: policy-security-context + name: check-container-security-context spec: + # validationFailureAction: "audit" rules: - - name: validate-runAsNonRoot + - name: check-root-user + exclude: + resources: + namespaces: + - kube-system match: resources: kinds: - - Deployment - selector : - matchLabels: - test: psp + - Pod validate: - message: "security context 'runAsNonRoot' shoud be set to true" - pattern: - spec: - template: - spec: + message: "Root user is not allowed. Set runAsNonRoot to true." + anyPattern: + - spec: + securityContext: + runAsNonRoot: true + - spec: + containers: + - name: "*" securityContext: runAsNonRoot: true \ No newline at end of file diff --git a/examples/resource_validate_nonRootUser.yaml b/examples/resource_validate_nonRootUser.yaml index 7e6370ccd8..fe64cbe4bd 100644 --- a/examples/resource_validate_nonRootUser.yaml +++ b/examples/resource_validate_nonRootUser.yaml @@ -1,21 +1,10 @@ -apiVersion: apps/v1 -kind: Deployment +apiVersion: v1 +kind: Pod metadata: - name: psp-demo-unprivileged - labels: - test: psp + name: sec-ctx-unprivileged spec: - replicas: 1 - selector: - matchLabels: - test: psp - template: - metadata: - labels: - test: psp - spec: - securityContext: - runAsNonRoot: true - containers: - - name: sec-ctx-unprivileged - image: nginxinc/nginx-unprivileged + # securityContext: + # runAsNonRoot: true + containers: + - name: imagen-with-hostpath + image: nginxinc/nginx-unprivileged diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 2d8e36560c..ad89a79070 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -1,7 +1,6 @@ package engine import ( - "encoding/json" "errors" "fmt" "path/filepath" @@ -18,18 +17,6 @@ import ( // Validate handles validating admission request // Checks the target resources for rules defined in the policy func Validate(policy kyverno.Policy, resource unstructured.Unstructured) EngineResponse { - resourceRaw, err := resource.MarshalJSON() - if err != nil { - glog.V(4).Infof("Skip processing validating rule, unable to marshal resource : %v\n", err) - return EngineResponse{PatchedResource: resource} - } - - var resourceInt interface{} - if err := json.Unmarshal(resourceRaw, &resourceInt); err != nil { - glog.V(4).Infof("unable to unmarshal resource : %v\n", err) - return EngineResponse{PatchedResource: resource} - } - var ruleInfos []info.RuleInfo for _, rule := range policy.Spec.Rules { @@ -46,22 +33,54 @@ func Validate(policy kyverno.Policy, resource unstructured.Unstructured) EngineR continue } - ruleInfo := info.NewRuleInfo(rule.Name, info.Validation) - - err := validateResourceWithPattern(resourceInt, rule.Validation.Pattern) - if err != nil { - ruleInfo.Fail() - ruleInfo.Addf("Failed to apply pattern: %v.", err) - } else { - ruleInfo.Add("Pattern succesfully validated") - glog.V(4).Infof("pattern validated succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName()) - } + ruleInfo := validatePatterns(resource, rule.Validation, rule.Name) ruleInfos = append(ruleInfos, ruleInfo) } return EngineResponse{RuleInfos: ruleInfos} } +// validatePatterns validate pattern and anyPattern +func validatePatterns(resource unstructured.Unstructured, validation kyverno.Validation, ruleName string) info.RuleInfo { + var errs []error + ruleInfo := info.NewRuleInfo(ruleName, info.Validation) + + if validation.Pattern != nil { + err := validateResourceWithPattern(resource.Object, validation.Pattern) + if err != nil { + ruleInfo.Fail() + ruleInfo.Addf("Failed to apply pattern: %v", err) + } else { + ruleInfo.Add("Pattern succesfully validated") + glog.V(4).Infof("pattern validated succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName()) + } + return ruleInfo + } + + if validation.AnyPattern != nil { + for _, pattern := range validation.AnyPattern { + if err := validateResourceWithPattern(resource.Object, pattern); err != nil { + errs = append(errs, err) + } + } + + failedPattern := len(errs) + patterns := len(validation.AnyPattern) + + // all pattern fail + if failedPattern == patterns { + ruleInfo.Fail() + ruleInfo.Addf("None of anyPattern succeed: %v", errs) + } else { + ruleInfo.Addf("%d/%d patterns succesfully validated", patterns-failedPattern, patterns) + glog.V(4).Infof("%d/%d patterns validated succesfully on resource %s/%s", patterns-failedPattern, patterns, resource.GetNamespace(), resource.GetName()) + } + return ruleInfo + } + + return info.RuleInfo{} +} + // validateResourceWithPattern is a start of element-by-element validation process // It assumes that validation is started from root, so "/" is passed func validateResourceWithPattern(resource, pattern interface{}) error { diff --git a/pkg/webhooks/policyvalidation.go b/pkg/webhooks/policyvalidation.go index 331210a6ac..50764dfdb2 100644 --- a/pkg/webhooks/policyvalidation.go +++ b/pkg/webhooks/policyvalidation.go @@ -25,7 +25,10 @@ func (ws *WebhookServer) HandlePolicyValidation(request *v1beta1.AdmissionReques } if err := json.Unmarshal(raw, &policy); err != nil { glog.Errorf("Failed to unmarshal policy admission request, err %v\n", err) - return &v1beta1.AdmissionResponse{Allowed: false} + return &v1beta1.AdmissionResponse{Allowed: false, + Result: &metav1.Status{ + Message: fmt.Sprintf("Failed to unmarshal policy admission request err %v", err), + }} } if request.Operation != v1beta1.Delete { @@ -50,14 +53,16 @@ func (ws *WebhookServer) validatePolicy(policy *kyverno.Policy) *v1beta1.Admissi func (ws *WebhookServer) validateOverlayPattern(policy *kyverno.Policy) *v1beta1.AdmissionResponse { for _, rule := range policy.Spec.Rules { - if !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) { - if rule.Validation.Pattern == nil && rule.Validation.AnyPattern == nil { - return &v1beta1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Message: "Invalid policy, either pattern or anyPattern found in policy spec", - }, - } + if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) { + continue + } + + if rule.Validation.Pattern == nil && len(rule.Validation.AnyPattern) == 0 { + return &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Message: fmt.Sprintf("Invalid policy, neither pattern nor anyPattern found in validate rule %s", rule.Name), + }, } } }