From 61c1ea5a49895ca1f45f074d39500794fb2587ad Mon Sep 17 00:00:00 2001 From: shivkumar dudhani Date: Thu, 31 Oct 2019 13:04:56 -0700 Subject: [PATCH] use validatepattern in generate rule to check for subset existance --- pkg/engine/generation.go | 36 +--- pkg/engine/pattern.go | 25 ++- pkg/utils/json.go | 188 ------------------ pkg/webhooks/validation.go | 2 - .../require_namespace_quota.yaml | 8 +- ...policy_validate_user_group_fsgroup_id.yaml | 6 +- 6 files changed, 35 insertions(+), 230 deletions(-) diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 91bfc451db..ff62a72de7 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -1,7 +1,6 @@ package engine import ( - "encoding/json" "time" "fmt" @@ -9,7 +8,6 @@ import ( "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" client "github.com/nirmata/kyverno/pkg/dclient" - "github.com/nirmata/kyverno/pkg/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -149,38 +147,12 @@ func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, rul //checkResource checks if the config is present in th eresource func checkResource(config interface{}, resource *unstructured.Unstructured) (bool, error) { - var err error - objByte, err := resource.MarshalJSON() + // we are checking if config is a subset of resource with default pattern + path, err := validateResourceWithPattern(resource.Object, config) if err != nil { - // unable to parse the json + glog.V(4).Infof("config not a subset of resource. failed at path %s: %v", path, err) return false, err } - err = resource.UnmarshalJSON(objByte) - if err != nil { - // unable to parse the json - return false, err - } - // marshall and unmarshall json to verify if its right format - configByte, err := json.Marshal(config) - if err != nil { - // unable to marshall the config - return false, err - } - var configData interface{} - err = json.Unmarshal(configByte, &configData) - if err != nil { - // unable to unmarshall - return false, err - } - - var objData interface{} - err = json.Unmarshal(objByte, &objData) - if err != nil { - // unable to unmarshall - return false, err - } - - // Check if the config is a subset of resource - return utils.JSONsubsetValue(configData, objData), nil + return true, nil } diff --git a/pkg/engine/pattern.go b/pkg/engine/pattern.go index 185f993e2e..421398509f 100644 --- a/pkg/engine/pattern.go +++ b/pkg/engine/pattern.go @@ -3,6 +3,7 @@ package engine import ( "math" "regexp" + "strconv" "strings" "github.com/golang/glog" @@ -91,6 +92,14 @@ func validateValueWithIntPattern(value interface{}, pattern int64) bool { glog.Warningf("Expected int, found float: %f\n", typedValue) return false + case string: + // extract int64 from string + int64Num, err := strconv.ParseInt(typedValue, 10, 64) + if err != nil { + glog.Warningf("Failed to parse int64 from string: %v", err) + return false + } + return int64Num == pattern default: glog.Warningf("Expected int, found: %T\n", value) return false @@ -105,11 +114,25 @@ func validateValueWithFloatPattern(value interface{}, pattern float64) bool { if pattern == math.Trunc(pattern) { return int(pattern) == value } - + glog.Warningf("Expected float, found int: %d\n", typedValue) + return false + case int64: + // check that float has no fraction + if pattern == math.Trunc(pattern) { + return int64(pattern) == value + } glog.Warningf("Expected float, found int: %d\n", typedValue) return false case float64: return typedValue == pattern + case string: + // extract float64 from string + float64Num, err := strconv.ParseFloat(typedValue, 64) + if err != nil { + glog.Warningf("Failed to parse float64 from string: %v", err) + return false + } + return float64Num == pattern default: glog.Warningf("Expected float, found: %T\n", value) return false diff --git a/pkg/utils/json.go b/pkg/utils/json.go index 651e86c049..471714958f 100644 --- a/pkg/utils/json.go +++ b/pkg/utils/json.go @@ -1,193 +1,5 @@ package utils -import ( - "github.com/golang/glog" -) - -//JSONsubsetValue checks if JSON a is contained in JSON b -func JSONsubsetValue(a interface{}, b interface{}) bool { - switch typed := a.(type) { - case bool: - bv, ok := b.(bool) - if !ok { - glog.Errorf("expected bool found %T", b) - return false - } - av, _ := a.(bool) - if av == bv { - return true - } - case int: - bv, ok := b.(int) - if !ok { - glog.Errorf("expected int found %T", b) - return false - } - av, _ := a.(int) - if av == bv { - return true - } - case float64: - bv, ok := b.(float64) - if !ok { - glog.Errorf("expected float64 found %T", b) - return false - } - av, _ := a.(float64) - if av == bv { - return true - } - - case string: - bv, ok := b.(string) - if !ok { - glog.Errorf("expected string found %T", b) - return false - } - av, _ := a.(string) - if av == bv { - return true - } - - case map[string]interface{}: - bv, ok := b.(map[string]interface{}) - if !ok { - glog.Errorf("expected map[string]interface{} found %T", b) - return false - } - av, _ := a.(map[string]interface{}) - return subsetMap(av, bv) - case []interface{}: - // TODO: verify the logic - bv, ok := b.([]interface{}) - if !ok { - glog.Errorf("expected []interface{} found %T", b) - return false - } - av, _ := a.([]interface{}) - return subsetSlice(av, bv) - default: - glog.Errorf("Unspported type %s", typed) - - } - return false -} - -func subsetMap(a, b map[string]interface{}) bool { - // check if keys are present - for k := range a { - if _, ok := b[k]; !ok { - glog.Errorf("key %s, not present in resource", k) - return false - } - } - // check if values for the keys match - for ak, av := range a { - bv := b[ak] - if !JSONsubsetValue(av, bv) { - return false - } - } - return true -} - -func containsInt(a interface{}, b []interface{}) bool { - switch typed := a.(type) { - case bool: - for _, bv := range b { - bv, ok := bv.(bool) - if !ok { - return false - } - av, _ := a.(bool) - - if bv == av { - return true - } - } - case int: - for _, bv := range b { - bv, ok := bv.(int) - if !ok { - return false - } - av, _ := a.(int) - - if bv == av { - return true - } - } - case float64: - for _, bv := range b { - bv, ok := bv.(float64) - if !ok { - return false - } - av, _ := a.(float64) - - if bv == av { - return true - } - } - case string: - for _, bv := range b { - bv, ok := bv.(string) - if !ok { - return false - } - av, _ := a.(string) - - if bv == av { - return true - } - } - case map[string]interface{}: - for _, bv := range b { - bv, ok := bv.(map[string]interface{}) - if !ok { - return false - } - av, _ := a.(map[string]interface{}) - if subsetMap(av, bv) { - return true - } - } - case []interface{}: - for _, bv := range b { - bv, ok := bv.([]interface{}) - if !ok { - return false - } - av, _ := a.([]interface{}) - if JSONsubsetValue(av, bv) { - return true - } - } - default: - glog.Errorf("Unspported type %s", typed) - } - - return false -} - -func subsetSlice(a, b []interface{}) bool { - // if empty - if len(a) == 0 { - return true - } - // check if len is not greater - if len(a) > len(b) { - return false - } - - for _, av := range a { - if !containsInt(av, b) { - return false - } - } - return true -} - // JoinPatches joins array of serialized JSON patches to the single JSONPatch array func JoinPatches(patches [][]byte) []byte { var result []byte diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go index 7623f647ec..9afee7d96f 100644 --- a/pkg/webhooks/validation.go +++ b/pkg/webhooks/validation.go @@ -90,8 +90,6 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pat glog.V(2).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation) - // glog.V(4).Infof("Validating resource %s/%s/%s with policy %s with %d rules\n", resource.GetKind(), resource.GetNamespace(), resource.GetName(), policy.ObjectMeta.Name, len(policy.Spec.Rules)) - engineResponse := engine.Validate(*policy, *resource) engineResponses = append(engineResponses, engineResponse) // Gather policy application statistics diff --git a/samples/best_practices/require_namespace_quota.yaml b/samples/best_practices/require_namespace_quota.yaml index f00a091b98..134af75a51 100644 --- a/samples/best_practices/require_namespace_quota.yaml +++ b/samples/best_practices/require_namespace_quota.yaml @@ -20,7 +20,7 @@ spec: data: spec: hard: - requests.cpu: '4' - requests.memory: "16Gi" - limits.cpu: '4' - limits.memory: "16Gi" \ No newline at end of file + requests.cpu: 4 + requests.memory: 16Gi + limits.cpu: 4 + limits.memory: 16Gi \ No newline at end of file diff --git a/samples/more/policy_validate_user_group_fsgroup_id.yaml b/samples/more/policy_validate_user_group_fsgroup_id.yaml index ba6bc3bded..853d8fd06e 100644 --- a/samples/more/policy_validate_user_group_fsgroup_id.yaml +++ b/samples/more/policy_validate_user_group_fsgroup_id.yaml @@ -20,7 +20,7 @@ spec: pattern: spec: securityContext: - runAsUser: '1000' + runAsUser: 1000 - name: validate-groupid match: resources: @@ -31,7 +31,7 @@ spec: pattern: spec: securityContext: - runAsGroup: '3000' + runAsGroup: 3000 - name: validate-fsgroup match: resources: @@ -42,7 +42,7 @@ spec: pattern: spec: securityContext: - fsGroup: '2000' + fsGroup: 2000 # Alls processes inside the pod can be made to run with specific user and groupID by setting runAsUser and runAsGroup respectively. # fsGroup can be specified to make sure any file created in the volume with have the specified groupID. # The above parameters can also be used in a validate policy to restrict user & group IDs. \ No newline at end of file