From 44ba5dbd8f6c7653c4f5f818d52e8eb4776f8d17 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Wed, 15 May 2019 14:05:28 +0300 Subject: [PATCH 1/9] Removed TODO comment about ProcessExisting --- pkg/engine/engine.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index cd8b41d400..598ed361b2 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -25,7 +25,6 @@ type PolicyEngine interface { // ProcessExisting should be called from policy controller // when there is an create / update of the policy // we should process the policy on matched resources, generate violations accordingly - // TODO: This method should not be in PolicyEngine. Validate will do this work instead ProcessExisting(policy types.Policy, rawResource []byte) ([]violation.Info, []event.Info, error) // TODO: Add Generate method From 10e8d2cfe03cd27798d252057f30af24ebad3816 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Wed, 15 May 2019 14:25:32 +0300 Subject: [PATCH 2/9] Moved common utils for mutation, validation and generation to pkg/engine/utils --- pkg/engine/engine.go | 6 +- pkg/engine/generation.go | 5 +- pkg/engine/mutation.go | 2 +- pkg/engine/mutation/checkRules.go | 44 --------------- pkg/engine/{mutation => }/utils.go | 73 +++++++++++++------------ pkg/engine/{mutation => }/utils_test.go | 2 +- pkg/engine/validation.go | 3 +- pkg/webhooks/server.go | 4 +- 8 files changed, 47 insertions(+), 92 deletions(-) delete mode 100644 pkg/engine/mutation/checkRules.go rename pkg/engine/{mutation => }/utils.go (99%) rename pkg/engine/{mutation => }/utils_test.go (97%) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 598ed361b2..0a8d70a765 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -80,9 +80,9 @@ func (p *policyEngine) processRuleOnResource(policyName string, rule types.Rule, var violationInfo violation.Info var eventInfos []event.Info - resourceKind := mutation.ParseKindFromObject(rawResource) - resourceName := mutation.ParseNameFromObject(rawResource) - resourceNamespace := mutation.ParseNamespaceFromObject(rawResource) + resourceKind := ParseKindFromObject(rawResource) + resourceName := ParseNameFromObject(rawResource) + resourceNamespace := ParseNamespaceFromObject(rawResource) rulePatchesProcessed, err := mutation.ProcessPatches(rule.Mutation.Patches, nil) if err != nil { diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index dc8f7cb231..24bb81f09b 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -4,18 +4,17 @@ import ( "fmt" kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - "github.com/nirmata/kube-policy/pkg/engine/mutation" ) // TODO: To be reworked due to spec policy-v2 // Applies "configMapGenerator" and "secretGenerator" described in PolicyRule func (p *policyEngine) applyRuleGenerators(rawResource []byte, rule kubepolicy.Rule) error { - kind := mutation.ParseKindFromObject(rawResource) + kind := ParseKindFromObject(rawResource) // configMapGenerator and secretGenerator can be applied only to namespaces if kind == "Namespace" { - namespaceName := mutation.ParseNameFromObject(rawResource) + namespaceName := ParseNameFromObject(rawResource) err := p.applyConfigGenerator(rule.Generation, namespaceName, "ConfigMap") if err == nil { diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index d6aeac688b..8cc15dd7a5 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -23,7 +23,7 @@ func (p *policyEngine) Mutate(policy kubepolicy.Policy, rawResource []byte, gvk continue } - ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk) + ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk) if err != nil { p.logger.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err) continue diff --git a/pkg/engine/mutation/checkRules.go b/pkg/engine/mutation/checkRules.go deleted file mode 100644 index bcd73a0840..0000000000 --- a/pkg/engine/mutation/checkRules.go +++ /dev/null @@ -1,44 +0,0 @@ -package mutation - -import ( - "github.com/minio/minio/pkg/wildcard" - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// kind is the type of object being manipulated -// Checks requests kind, name and labels to fit the policy -func IsRuleApplicableToResource(resourceRaw []byte, description types.ResourceDescription) (bool, error) { - kind := ParseKindFromObject(resourceRaw) - if description.Kind != kind { - return false, nil - } - - if resourceRaw != nil { - meta := ParseMetadataFromObject(resourceRaw) - name := ParseNameFromObject(resourceRaw) - - if description.Name != nil { - - if !wildcard.Match(*description.Name, name) { - return false, nil - } - } - - if description.Selector != nil { - selector, err := metav1.LabelSelectorAsSelector(description.Selector) - - if err != nil { - return false, err - } - - labelMap := ParseLabelsFromMetadata(meta) - - if !selector.Matches(labelMap) { - return false, nil - } - - } - } - return true, nil -} diff --git a/pkg/engine/mutation/utils.go b/pkg/engine/utils.go similarity index 99% rename from pkg/engine/mutation/utils.go rename to pkg/engine/utils.go index ad433932be..f4ce55a8cb 100644 --- a/pkg/engine/mutation/utils.go +++ b/pkg/engine/utils.go @@ -1,4 +1,4 @@ -package mutation +package engine import ( "encoding/json" @@ -6,10 +6,46 @@ import ( "github.com/minio/minio/pkg/wildcard" kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" ) +// ResourceMeetsRules checks requests kind, name and labels to fit the policy +func ResourceMeetsRules(resourceRaw []byte, description kubepolicy.ResourceDescription, gvk metav1.GroupVersionKind) (bool, error) { + if description.Kind != gvk.Kind { + return false, nil + } + + if resourceRaw != nil { + meta := ParseMetadataFromObject(resourceRaw) + name := ParseNameFromObject(resourceRaw) + + if description.Name != nil { + + if !wildcard.Match(*description.Name, name) { + return false, nil + } + } + + if description.Selector != nil { + selector, err := metav1.LabelSelectorAsSelector(description.Selector) + + if err != nil { + return false, err + } + + labelMap := ParseLabelsFromMetadata(meta) + + if !selector.Matches(labelMap) { + return false, nil + } + + } + } + return true, nil +} + func ParseMetadataFromObject(bytes []byte) map[string]interface{} { var objectJSON map[string]interface{} json.Unmarshal(bytes, &objectJSON) @@ -68,38 +104,3 @@ func ParseRegexPolicyResourceName(policyResourceName string) (string, bool) { } return strings.Trim(regex[1], " "), true } - -// ResourceMeetsRules checks requests kind, name and labels to fit the policy -func ResourceMeetsRules(resourceRaw []byte, description kubepolicy.ResourceDescription, gvk metav1.GroupVersionKind) (bool, error) { - if description.Kind != gvk.Kind { - return false, nil - } - - if resourceRaw != nil { - meta := ParseMetadataFromObject(resourceRaw) - name := ParseNameFromObject(resourceRaw) - - if description.Name != nil { - - if !wildcard.Match(*description.Name, name) { - return false, nil - } - } - - if description.Selector != nil { - selector, err := metav1.LabelSelectorAsSelector(description.Selector) - - if err != nil { - return false, err - } - - labelMap := ParseLabelsFromMetadata(meta) - - if !selector.Matches(labelMap) { - return false, nil - } - - } - } - return true, nil -} diff --git a/pkg/engine/mutation/utils_test.go b/pkg/engine/utils_test.go similarity index 97% rename from pkg/engine/mutation/utils_test.go rename to pkg/engine/utils_test.go index f8473ae287..10a0b32a62 100644 --- a/pkg/engine/mutation/utils_test.go +++ b/pkg/engine/utils_test.go @@ -1,4 +1,4 @@ -package mutation +package engine import ( "testing" diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 726d830944..4485dff5b7 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -5,7 +5,6 @@ import ( "fmt" kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - "github.com/nirmata/kube-policy/pkg/engine/mutation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -27,7 +26,7 @@ func (p *policyEngine) Validate(policy kubepolicy.Policy, rawResource []byte, gv continue } - ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk) + ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk) if err != nil { p.logger.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err) continue diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 528af3f817..c4ef046aba 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -152,8 +152,8 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be allPatches = append(allPatches, policyPatches...) if len(policyPatches) > 0 { - namespace := mutation.ParseNamespaceFromObject(request.Object.Raw) - name := mutation.ParseNameFromObject(request.Object.Raw) + namespace := engine.ParseNamespaceFromObject(request.Object.Raw) + name := engine.ParseNameFromObject(request.Object.Raw) ws.logger.Printf("Policy %s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name) } } From 5be337471bb03c3e7cdc2c97f92ec10cffacf56a Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Wed, 15 May 2019 14:26:32 +0300 Subject: [PATCH 3/9] Removed pkg/engine/utils_test.go because it's never used --- pkg/engine/utils_test.go | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 pkg/engine/utils_test.go diff --git a/pkg/engine/utils_test.go b/pkg/engine/utils_test.go deleted file mode 100644 index 10a0b32a62..0000000000 --- a/pkg/engine/utils_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package engine - -import ( - "testing" -) - -func assertEqDataImpl(t *testing.T, expected, actual []byte, formatModifier string) { - if len(expected) != len(actual) { - t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual) - return - } - - for idx, val := range actual { - if val != expected[idx] { - t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual) - } - } -} - -func assertEqData(t *testing.T, expected, actual []byte) { - assertEqDataImpl(t, expected, actual, "%x") -} - -func assertEqStringAndData(t *testing.T, str string, data []byte) { - assertEqDataImpl(t, []byte(str), data, "%s") -} From 800eb9b92d2c64b1ef3b4a931c7e80c37ca72705 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Wed, 15 May 2019 16:15:16 +0300 Subject: [PATCH 4/9] Removed excess validation --- pkg/engine/generation.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index e5cb259942..9785487347 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -1,11 +1,9 @@ package engine import ( - "fmt" "log" kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - "github.com/nirmata/kube-policy/pkg/engine/mutation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -37,7 +35,7 @@ func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers continue } - ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk) + ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk) if err != nil { log.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err) continue @@ -62,17 +60,12 @@ func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers // Applies "configMapGenerator" and "secretGenerator" described in PolicyRule // TODO: plan to support all kinds of generator func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation) ([]GenerationResponse, error) { - var generateResps []GenerationResponse + var generationResponse []GenerationResponse if generator == nil { return nil, nil } - err := generator.Validate() - if err != nil { - return nil, fmt.Errorf("Generator for '%s' is invalid: %s", generator.Kind, err) - } - - namespaceName := mutation.ParseNameFromObject(rawResource) - generateResps = append(generateResps, GenerationResponse{generator, namespaceName}) - return generateResps, nil + namespaceName := ParseNameFromObject(rawResource) + generationResponse = append(generationResponse, GenerationResponse{generator, namespaceName}) + return generationResponse, nil } From 281dc257b9ddd781eee6f8aa487fe4929dc75839 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Wed, 15 May 2019 19:25:49 +0300 Subject: [PATCH 5/9] Reworking validation logic due to the anchor feature --- pkg/engine/validation.go | 86 +++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 4882e39ac6..79c3bd7f6a 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -9,6 +9,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// Validate handles validating admission request +// Checks the target resourse for rules defined in the policy func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) bool { var resource interface{} json.Unmarshal(rawResource, &resource) @@ -53,7 +55,44 @@ func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers return allowed } -func traverseAndValidate(resourcePart, patternPart interface{}) error { +func validateMap(resourcePart, patternPart interface{}) error { + pattern := patternPart.(map[string]interface{}) + resource, ok := resourcePart.(map[string]interface{}) + + if !ok { + return fmt.Errorf("Validating error: expected Map, found %T", resourcePart) + } + + for key, value := range pattern { + err := validateMapElement(resource[key], value) + + if err != nil { + return err + } + } + + return nil +} + +func validateArray(resourcePart, patternPart interface{}) error { + pattern := patternPart.([]interface{}) + resource, ok := resourcePart.([]interface{}) + + if !ok { + return fmt.Errorf("Validating error: expected Map, found %T", resourcePart) + } + + patternElem := pattern[0] + switch typedElem := patternElem.(type) { + case map[string]interface{}: + + default: + return nil + + return nil +} + +func validateMapElement(resourcePart, patternPart interface{}) error { switch pattern := patternPart.(type) { case map[string]interface{}: dictionary, ok := resourcePart.(map[string]interface{}) @@ -62,12 +101,7 @@ func traverseAndValidate(resourcePart, patternPart interface{}) error { return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart) } - var err error - for key, value := range pattern { - err = traverseAndValidate(dictionary[key], value) - } - return err - + return validateMap(dictionary, pattern) case []interface{}: array, ok := resourcePart.([]interface{}) @@ -75,16 +109,15 @@ func traverseAndValidate(resourcePart, patternPart interface{}) error { return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart) } - var err error - for i, value := range pattern { - err = traverseAndValidate(array[i], value) - } - return err + return validateArray(array, pattern) case string: - str := resourcePart.(string) - if !checkForWildcard(str, pattern) { - return fmt.Errorf("Value %s has not passed wildcard check %s", str, pattern) + str, ok := resourcePart.(string) + + if !ok { + return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart) } + + return validateSingleString(str, pattern) default: return fmt.Errorf("Received unknown type: %T", patternPart) } @@ -92,6 +125,18 @@ func traverseAndValidate(resourcePart, patternPart interface{}) error { return nil } +func validateSingleString(str, pattern string) error { + if wrappedWithParentheses(str) { + + } + + return nil +} + +func wrappedWithParentheses(str string) bool { + return (str[0] == '(' && str[len(str)-1] == ')') +} + func checkForWildcard(value, pattern string) bool { return value == pattern } @@ -99,3 +144,14 @@ func checkForWildcard(value, pattern string) bool { func checkForOperator(value int, pattern string) bool { return true } + +func getAnchorsFromMap(patternMap map[string]interface{}) map[string]string { + result := make(map[string]Vertex) + + for key, value := range patternMap { + str, ok := value.(string) + if ok { + result[key] = str + } + } +} From 8e6552177568bb5983d5684599abb14b13dce8f5 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Thu, 16 May 2019 17:37:05 +0300 Subject: [PATCH 6/9] Finished implementing validation patterns --- pkg/engine/validation.go | 169 +++++++++++++++++++++++++++++---------- 1 file changed, 126 insertions(+), 43 deletions(-) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 79c3bd7f6a..787ebeaf3d 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -44,61 +44,87 @@ func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers continue } - if err := traverseAndValidate(resource, rule.Validation.Pattern); err != nil { - log.Printf("Validation with the rule %s has failed %s: %s\n", rule.Name, err.Error(), *rule.Validation.Message) + if !validateMap(resource, rule.Validation.Pattern) { + log.Printf("Validation with the rule %s has failed: %s\n", rule.Name, *rule.Validation.Message) allowed = false } else { - log.Printf("Validation rule %s is successful %s: %s\n", rule.Name, err.Error(), *rule.Validation.Message) + log.Printf("Validation rule %s is successful\n", rule.Name) } } return allowed } -func validateMap(resourcePart, patternPart interface{}) error { +func validateMap(resourcePart, patternPart interface{}) bool { pattern := patternPart.(map[string]interface{}) resource, ok := resourcePart.(map[string]interface{}) if !ok { - return fmt.Errorf("Validating error: expected Map, found %T", resourcePart) + fmt.Printf("Validating error: expected Map, found %T\n", resourcePart) + return false } for key, value := range pattern { - err := validateMapElement(resource[key], value) - - if err != nil { - return err + if !validateMapElement(resource[key], value) { + return false } } - return nil + return true } -func validateArray(resourcePart, patternPart interface{}) error { - pattern := patternPart.([]interface{}) - resource, ok := resourcePart.([]interface{}) +func validateArray(resourcePart, patternPart interface{}) bool { + patternArray := patternPart.([]interface{}) + resourceArray, ok := resourcePart.([]interface{}) if !ok { - return fmt.Errorf("Validating error: expected Map, found %T", resourcePart) + fmt.Printf("Validating error: expected array, found %T\n", resourcePart) + return false } - patternElem := pattern[0] - switch typedElem := patternElem.(type) { + switch pattern := patternArray[0].(type) { case map[string]interface{}: - - default: - return nil + anchors, err := getAnchorsFromMap(pattern) + if err != nil { + fmt.Printf("Validating error: %v\n", err) + return false + } - return nil + for _, value := range resourceArray { + resource, ok := value.(map[string]interface{}) + if !ok { + fmt.Printf("Validating error: expected Map, found %T\n", resourcePart) + return false + } + + if skipArrayObject(resource, anchors) { + continue + } + + // CHECK OBJECT WITH PATTERN WITHOUT PARENTHESES + + } + + return true + default: + for _, value := range resourceArray { + if !checkSingleValue(value, patternArray[0]) { + return false + } + } + } + + return true } -func validateMapElement(resourcePart, patternPart interface{}) error { +func validateMapElement(resourcePart, patternPart interface{}) bool { switch pattern := patternPart.(type) { case map[string]interface{}: dictionary, ok := resourcePart.(map[string]interface{}) if !ok { - return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart) + fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart) + return false } return validateMap(dictionary, pattern) @@ -106,7 +132,8 @@ func validateMapElement(resourcePart, patternPart interface{}) error { array, ok := resourcePart.([]interface{}) if !ok { - return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart) + fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart) + return false } return validateArray(array, pattern) @@ -114,44 +141,100 @@ func validateMapElement(resourcePart, patternPart interface{}) error { str, ok := resourcePart.(string) if !ok { - return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart) + fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart) + return false } - return validateSingleString(str, pattern) + if wrappedWithParentheses(pattern) { + pattern = pattern[1 : len(pattern)-2] + } + + return checkSingleValue(str, pattern) default: - return fmt.Errorf("Received unknown type: %T", patternPart) + fmt.Printf("Validating error: unknown type in map: %T\n", patternPart) + return false } - - return nil } -func validateSingleString(str, pattern string) error { - if wrappedWithParentheses(str) { +func getAnchorsFromMap(pattern map[string]interface{}) (map[string]string, error) { + result := make(map[string]string) + for key, value := range pattern { + if wrappedWithParentheses(key) { + + str, ok := value.(string) + if !ok { + return result, fmt.Errorf("Validating error: anchors must have string value, found %T", value) + } + + result[key] = str + } } - return nil + return result, nil } -func wrappedWithParentheses(str string) bool { - return (str[0] == '(' && str[len(str)-1] == ')') +func skipArrayObject(object map[string]interface{}, anchors map[string]string) bool { + for wrappedKey, pattern := range anchors { + key := wrappedKey[1 : len(wrappedKey)-2] + + value, ok := object[key] + if !ok { + return true + } + + if !checkSingleValue(value, pattern) { + return true + } + } + + return false +} + +func checkSingleValue(value, pattern interface{}) bool { + switch typedPattern := pattern.(type) { + case string: + switch typedValue := value.(type) { + case string: + return checkForWildcard(typedValue, typedPattern) + case float64: + return checkForOperator(typedValue, typedPattern) + case int64: + return checkForOperator(float64(typedValue), typedPattern) + default: + fmt.Printf("Validating error: expected string or numerical type, found %T, pattern: %s\n", value, typedPattern) + return false + } + case float64: + num, ok := value.(float64) + if !ok { + fmt.Printf("Validating error: expected float, found %T\n", value) + return false + } + + return typedPattern == num + case int64: + num, ok := value.(int64) + if !ok { + fmt.Printf("Validating error: expected int, found %T\n", value) + return false + } + + return typedPattern == num + default: + fmt.Printf("Validating error: expected pattern (string or numerical type), found %T\n", pattern) + return false + } } func checkForWildcard(value, pattern string) bool { return value == pattern } -func checkForOperator(value int, pattern string) bool { +func checkForOperator(value float64, pattern string) bool { return true } -func getAnchorsFromMap(patternMap map[string]interface{}) map[string]string { - result := make(map[string]Vertex) - - for key, value := range patternMap { - str, ok := value.(string) - if ok { - result[key] = str - } - } +func wrappedWithParentheses(str string) bool { + return (str[0] == '(' && str[len(str)-1] == ')') } From 7f3500a6fb8c8790e3499b3bdfe737db734ca966 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Thu, 16 May 2019 19:31:02 +0300 Subject: [PATCH 7/9] Fixed errors in type validation --- pkg/engine/validation.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 787ebeaf3d..e892125a57 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -5,6 +5,8 @@ import ( "fmt" "log" + "github.com/minio/minio/pkg/wildcard" + kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -199,7 +201,7 @@ func checkSingleValue(value, pattern interface{}) bool { return checkForWildcard(typedValue, typedPattern) case float64: return checkForOperator(typedValue, typedPattern) - case int64: + case int: return checkForOperator(float64(typedValue), typedPattern) default: fmt.Printf("Validating error: expected string or numerical type, found %T, pattern: %s\n", value, typedPattern) @@ -213,8 +215,8 @@ func checkSingleValue(value, pattern interface{}) bool { } return typedPattern == num - case int64: - num, ok := value.(int64) + case int: + num, ok := value.(int) if !ok { fmt.Printf("Validating error: expected int, found %T\n", value) return false @@ -228,7 +230,7 @@ func checkSingleValue(value, pattern interface{}) bool { } func checkForWildcard(value, pattern string) bool { - return value == pattern + return wildcard.Match(pattern, value) } func checkForOperator(value float64, pattern string) bool { @@ -236,5 +238,9 @@ func checkForOperator(value float64, pattern string) bool { } func wrappedWithParentheses(str string) bool { + if len(str) < 2 { + return false + } + return (str[0] == '(' && str[len(str)-1] == ')') } From 354287ebb4e76e1f33006c1c306b9ecce9cb5f1e Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Thu, 16 May 2019 21:36:30 +0300 Subject: [PATCH 8/9] Fixed issue with checking parentheses --- pkg/engine/validation.go | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index e892125a57..d5a4c62dac 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -67,6 +67,10 @@ func validateMap(resourcePart, patternPart interface{}) bool { } for key, value := range pattern { + if wrappedWithParentheses(key) { + key = key[1 : len(key)-1] + } + if !validateMapElement(resource[key], value) { return false } @@ -103,8 +107,9 @@ func validateArray(resourcePart, patternPart interface{}) bool { continue } - // CHECK OBJECT WITH PATTERN WITHOUT PARENTHESES - + if !validateMap(resource, pattern) { + return false + } } return true @@ -147,10 +152,6 @@ func validateMapElement(resourcePart, patternPart interface{}) bool { return false } - if wrappedWithParentheses(pattern) { - pattern = pattern[1 : len(pattern)-2] - } - return checkSingleValue(str, pattern) default: fmt.Printf("Validating error: unknown type in map: %T\n", patternPart) @@ -158,27 +159,21 @@ func validateMapElement(resourcePart, patternPart interface{}) bool { } } -func getAnchorsFromMap(pattern map[string]interface{}) (map[string]string, error) { - result := make(map[string]string) +func getAnchorsFromMap(pattern map[string]interface{}) (map[string]interface{}, error) { + result := make(map[string]interface{}) for key, value := range pattern { if wrappedWithParentheses(key) { - - str, ok := value.(string) - if !ok { - return result, fmt.Errorf("Validating error: anchors must have string value, found %T", value) - } - - result[key] = str + result[key] = value } } return result, nil } -func skipArrayObject(object map[string]interface{}, anchors map[string]string) bool { - for wrappedKey, pattern := range anchors { - key := wrappedKey[1 : len(wrappedKey)-2] +func skipArrayObject(object, anchors map[string]interface{}) bool { + for key, pattern := range anchors { + key = key[1 : len(key)-1] value, ok := object[key] if !ok { From 00b667b6e4572ea836769bb2962fa8216ca57f06 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Thu, 16 May 2019 21:37:54 +0300 Subject: [PATCH 9/9] Added tests for validation logic --- pkg/engine/validation_test.go | 234 ++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 pkg/engine/validation_test.go diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go new file mode 100644 index 0000000000..5302fd6988 --- /dev/null +++ b/pkg/engine/validation_test.go @@ -0,0 +1,234 @@ +package engine + +import ( + "encoding/json" + "testing" + + "gotest.tools/assert" +) + +func TestWrappedWithParentheses_StringIsWrappedWithParentheses(t *testing.T) { + str := "(something)" + assert.Assert(t, wrappedWithParentheses(str)) +} + +func TestWrappedWithParentheses_StringHasOnlyParentheses(t *testing.T) { + str := "()" + assert.Assert(t, wrappedWithParentheses(str)) +} + +func TestWrappedWithParentheses_StringHasNoParentheses(t *testing.T) { + str := "something" + assert.Assert(t, !wrappedWithParentheses(str)) +} + +func TestWrappedWithParentheses_StringHasLeftParentheses(t *testing.T) { + str := "(something" + assert.Assert(t, !wrappedWithParentheses(str)) +} + +func TestWrappedWithParentheses_StringHasRightParentheses(t *testing.T) { + str := "something)" + assert.Assert(t, !wrappedWithParentheses(str)) +} + +func TestWrappedWithParentheses_StringParenthesesInside(t *testing.T) { + str := "so)m(et(hin)g" + assert.Assert(t, !wrappedWithParentheses(str)) +} + +func TestWrappedWithParentheses_Empty(t *testing.T) { + str := "" + assert.Assert(t, !wrappedWithParentheses(str)) +} + +func TestCheckForWildcard_AsteriskTest(t *testing.T) { + pattern := "*" + value := "anything" + empty := "" + + assert.Assert(t, checkForWildcard(value, pattern)) + assert.Assert(t, checkForWildcard(empty, pattern)) +} + +func TestCheckForWildcard_LeftAsteriskTest(t *testing.T) { + pattern := "*right" + value := "leftright" + right := "right" + + assert.Assert(t, checkForWildcard(value, pattern)) + assert.Assert(t, checkForWildcard(right, pattern)) + + value = "leftmiddle" + middle := "middle" + + assert.Assert(t, !checkForWildcard(value, pattern)) + assert.Assert(t, !checkForWildcard(middle, pattern)) +} + +func TestCheckForWildcard_MiddleAsteriskTest(t *testing.T) { + pattern := "ab*ba" + value := "abbba" + assert.Assert(t, checkForWildcard(value, pattern)) + + value = "abbca" + assert.Assert(t, !checkForWildcard(value, pattern)) +} + +func TestCheckForWildcard_QuestionMark(t *testing.T) { + pattern := "ab?ba" + value := "abbba" + assert.Assert(t, checkForWildcard(value, pattern)) + + value = "abbbba" + assert.Assert(t, !checkForWildcard(value, pattern)) +} + +func TestCheckSingleValue_CheckInt(t *testing.T) { + pattern := 89 + value := 89 + assert.Assert(t, checkSingleValue(value, pattern)) + + value = 202 + assert.Assert(t, !checkSingleValue(value, pattern)) +} + +func TestCheckSingleValue_CheckFloat(t *testing.T) { + pattern := 89.9091 + value := 89.9091 + assert.Assert(t, checkSingleValue(value, pattern)) + + value = 89.9092 + assert.Assert(t, !checkSingleValue(value, pattern)) +} + +func TestCheckSingleValue_CheckOperatorMore(t *testing.T) { + pattern := ">10" + value := 89 + assert.Assert(t, checkSingleValue(value, pattern)) + + pattern = ">10" + floatValue := 89.901 + assert.Assert(t, checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckWildcard(t *testing.T) { + pattern := "nirmata_*" + value := "nirmata_awesome" + assert.Assert(t, checkSingleValue(value, pattern)) + + pattern = "nirmata_*" + value = "spasex_awesome" + assert.Assert(t, !checkSingleValue(value, pattern)) + + pattern = "g?t" + value = "git" + assert.Assert(t, checkSingleValue(value, pattern)) +} + +func TestSkipArrayObject_OneAnchor(t *testing.T) { + + rawAnchors := []byte(`{"(name)": "nirmata-*"}`) + rawResource := []byte(`{"name": "nirmata-resource", "namespace": "kube-policy", "object": { "label": "app", "array": [ 1, 2, 3 ]}}`) + + var resource, anchor map[string]interface{} + + json.Unmarshal(rawAnchors, &anchor) + json.Unmarshal(rawResource, &resource) + + assert.Assert(t, !skipArrayObject(resource, anchor)) +} + +func TestSkipArrayObject_OneNumberAnchorPass(t *testing.T) { + + rawAnchors := []byte(`{"(count)": 1}`) + rawResource := []byte(`{"name": "nirmata-resource", "count": 1, "namespace": "kube-policy", "object": { "label": "app", "array": [ 1, 2, 3 ]}}`) + + var resource, anchor map[string]interface{} + + json.Unmarshal(rawAnchors, &anchor) + json.Unmarshal(rawResource, &resource) + + assert.Assert(t, !skipArrayObject(resource, anchor)) +} + +func TestSkipArrayObject_TwoAnchorsPass(t *testing.T) { + rawAnchors := []byte(`{"(name)": "nirmata-*", "(namespace)": "kube-?olicy"}`) + rawResource := []byte(`{"name": "nirmata-resource", "namespace": "kube-policy", "object": { "label": "app", "array": [ 1, 2, 3 ]}}`) + + var resource, anchor map[string]interface{} + + json.Unmarshal(rawAnchors, &anchor) + json.Unmarshal(rawResource, &resource) + + assert.Assert(t, !skipArrayObject(resource, anchor)) +} + +func TestSkipArrayObject_TwoAnchorsSkip(t *testing.T) { + rawAnchors := []byte(`{"(name)": "nirmata-*", "(namespace)": "some-?olicy"}`) + rawResource := []byte(`{"name": "nirmata-resource", "namespace": "kube-policy", "object": { "label": "app", "array": [ 1, 2, 3 ]}}`) + + var resource, anchor map[string]interface{} + + json.Unmarshal(rawAnchors, &anchor) + json.Unmarshal(rawResource, &resource) + + assert.Assert(t, skipArrayObject(resource, anchor)) +} + +func TestGetAnchorsFromMap_ThereAreAnchors(t *testing.T) { + rawMap := []byte(`{"(name)": "nirmata-*", "notAnchor1": 123, "(namespace)": "kube-?olicy", "notAnchor2": "sample-text", "object": { "key1": "value1", "(key2)": "value2"}}`) + + var unmarshalled map[string]interface{} + json.Unmarshal(rawMap, &unmarshalled) + + actualMap, err := getAnchorsFromMap(unmarshalled) + assert.NilError(t, err) + assert.Equal(t, len(actualMap), 2) + assert.Equal(t, actualMap["(name)"].(string), "nirmata-*") + assert.Equal(t, actualMap["(namespace)"].(string), "kube-?olicy") +} + +func TestGetAnchorsFromMap_ThereAreNoAnchors(t *testing.T) { + rawMap := []byte(`{"name": "nirmata-*", "notAnchor1": 123, "namespace": "kube-?olicy", "notAnchor2": "sample-text", "object": { "key1": "value1", "(key2)": "value2"}}`) + + var unmarshalled map[string]interface{} + json.Unmarshal(rawMap, &unmarshalled) + + actualMap, err := getAnchorsFromMap(unmarshalled) + assert.NilError(t, err) + assert.Assert(t, len(actualMap) == 0) +} + +func TestValidateMapElement_TwoElementsInArrayOnePass(t *testing.T) { + rawPattern := []byte(`[ { "(name)": "nirmata-*", "object": [ { "(key1)": "value*", "key2": "value*" } ] } ]`) + rawMap := []byte(`[ { "name": "nirmata-1", "object": [ { "key1": "value1", "key2": "value2" } ] }, { "name": "nirmata-1", "object": [ { "key1": "not_value", "key2": "not_value" } ] } ]`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + assert.Assert(t, validateMapElement(resource, pattern)) +} + +func TestValidateMapElement_OneElementInArrayPass(t *testing.T) { + rawPattern := []byte(`[ { "(name)": "nirmata-*", "object": [ { "(key1)": "value*", "key2": "value*" } ] } ]`) + rawMap := []byte(`[ { "name": "nirmata-1", "object": [ { "key1": "value1", "key2": "value2" } ] } ]`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + assert.Assert(t, validateMapElement(resource, pattern)) +} + +func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) { + rawPattern := []byte(`[{"(name)": "nirmata-*", "object":[{"(key1)": "value*", "key2": "value*"}]}]`) + rawMap := []byte(`[ { "name": "nirmata-1", "object": [ { "key1": "value5", "key2": "1value1" } ] } ]`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + assert.Assert(t, !validateMapElement(resource, pattern)) +}