From 0a486a7f544a74d95ef05e72f35cd096541752ff Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Fri, 17 May 2019 14:03:06 +0300 Subject: [PATCH 1/3] I have finished implementing validation logic using TDD --- pkg/engine/validation.go | 89 ++++++++++++++++++++++- pkg/engine/validation_test.go | 128 +++++++++++++++++++++++++++++++++- 2 files changed, 213 insertions(+), 4 deletions(-) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index d5a4c62dac..40850c4e85 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "log" + "strconv" + "strings" "github.com/minio/minio/pkg/wildcard" @@ -11,6 +13,19 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// TODO: This operators are already implemented in kubernetes +type Operator string + +const ( + MoreEqual Operator = ">=" + LessEqual Operator = "<=" + NotEqual Operator = "!=" + More Operator = ">" + Less Operator = "<" +) + +// TODO: Refactor using State pattern + // 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 { @@ -228,8 +243,80 @@ func checkForWildcard(value, pattern string) bool { return wildcard.Match(pattern, value) } +func checkSingleOperator(value float64, operator string) bool { + if operatorVal, err := strconv.ParseFloat(operator, 64); err == nil { + return value == operatorVal + } + + if len(operator) < 2 { + fmt.Printf("Validating error: operator can't have less than 2 characters: %s\n", operator) + return false + } + + if operator[:len(MoreEqual)] == string(MoreEqual) { + operatorVal, err := strconv.ParseFloat(operator[len(MoreEqual):len(operator)], 64) + if err != nil { + fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) + return false + } + + return value >= operatorVal + } + + if operator[:len(LessEqual)] == string(LessEqual) { + operatorVal, err := strconv.ParseFloat(operator[len(LessEqual):len(operator)], 64) + if err != nil { + fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) + return false + } + + return value <= operatorVal + } + + if operator[:len(More)] == string(More) { + operatorVal, err := strconv.ParseFloat(operator[len(More):len(operator)], 64) + if err != nil { + fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) + return false + } + + return value > operatorVal + } + + if operator[:len(Less)] == string(Less) { + operatorVal, err := strconv.ParseFloat(operator[len(Less):len(operator)], 64) + if err != nil { + fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) + return false + } + + return value < operatorVal + } + + if operator[:len(NotEqual)] == string(NotEqual) { + operatorVal, err := strconv.ParseFloat(operator[len(NotEqual):len(operator)], 64) + if err != nil { + fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) + return false + } + + return value != operatorVal + } + + return false +} + func checkForOperator(value float64, pattern string) bool { - return true + operators := strings.Split(pattern, "|") + + for _, operator := range operators { + operator = strings.Replace(operator, " ", "", -1) + if checkSingleOperator(value, operator) { + return true + } + } + + return false } func wrappedWithParentheses(str string) bool { diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index 5302fd6988..68d07ca73e 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -102,16 +102,138 @@ func TestCheckSingleValue_CheckFloat(t *testing.T) { assert.Assert(t, !checkSingleValue(value, pattern)) } -func TestCheckSingleValue_CheckOperatorMore(t *testing.T) { - pattern := ">10" +func TestCheckSingleValue_CheckOperatorMoreEqual(t *testing.T) { + pattern := " >= 89 " value := 89 assert.Assert(t, checkSingleValue(value, pattern)) - pattern = ">10" + pattern = ">=10.0001" floatValue := 89.901 assert.Assert(t, checkSingleValue(floatValue, pattern)) } +func TestCheckSingleValue_CheckOperatorMoreEqualFail(t *testing.T) { + pattern := " >= 90 " + value := 89 + assert.Assert(t, !checkSingleValue(value, pattern)) + + pattern = ">=910.0001" + floatValue := 89.901 + assert.Assert(t, !checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorLessEqual(t *testing.T) { + pattern := " <= 1 " + value := 1 + assert.Assert(t, checkSingleValue(value, pattern)) + + pattern = "<=10.0001" + floatValue := 1.901 + assert.Assert(t, checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorLessEqualFail(t *testing.T) { + pattern := " <= 0.1558 " + value := 1 + assert.Assert(t, !checkSingleValue(value, pattern)) + + pattern = "<=10.0001" + floatValue := 12.901 + assert.Assert(t, !checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorMore(t *testing.T) { + pattern := " > 10 " + value := 89 + assert.Assert(t, checkSingleValue(value, pattern)) + + pattern = ">10.0001" + floatValue := 89.901 + assert.Assert(t, checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorMoreFail(t *testing.T) { + pattern := " > 89 " + value := 89 + assert.Assert(t, !checkSingleValue(value, pattern)) + + pattern = ">910.0001" + floatValue := 89.901 + assert.Assert(t, !checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorLess(t *testing.T) { + pattern := " < 10 " + value := 9 + assert.Assert(t, checkSingleValue(value, pattern)) + + pattern = "<10.0001" + floatValue := 9.901 + assert.Assert(t, checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorLessFail(t *testing.T) { + pattern := " < 10 " + value := 10 + assert.Assert(t, !checkSingleValue(value, pattern)) + + pattern = "<10.0001" + floatValue := 19.901 + assert.Assert(t, !checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorNotEqual(t *testing.T) { + pattern := " != 10 " + value := 9.99999 + assert.Assert(t, checkSingleValue(value, pattern)) + + pattern = "!=10.0001" + floatValue := 10.0000 + assert.Assert(t, checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorNotEqualFail(t *testing.T) { + pattern := " != 9.99999 " + value := 9.99999 + assert.Assert(t, !checkSingleValue(value, pattern)) + + pattern = "!=10" + floatValue := 10 + assert.Assert(t, !checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorEqual(t *testing.T) { + pattern := " 10.000001 " + value := 10.000001 + assert.Assert(t, checkSingleValue(value, pattern)) + + pattern = "10.000000" + floatValue := 10 + assert.Assert(t, checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckOperatorEqualFail(t *testing.T) { + pattern := " 10.000000 " + value := 10.000001 + assert.Assert(t, !checkSingleValue(value, pattern)) + + pattern = "10.000001" + floatValue := 10 + assert.Assert(t, !checkSingleValue(floatValue, pattern)) +} + +func TestCheckSingleValue_CheckSeveralOperators(t *testing.T) { + pattern := " <-1 | 10.000001 " + value := 10.000001 + assert.Assert(t, checkSingleValue(value, pattern)) + + value = -30 + assert.Assert(t, checkSingleValue(value, pattern)) + + value = 5 + assert.Assert(t, !checkSingleValue(value, pattern)) +} + func TestCheckSingleValue_CheckWildcard(t *testing.T) { pattern := "nirmata_*" value := "nirmata_awesome" From a6bb4b85648d0b35be85cf5d6af7dedd0fec6c8a Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Fri, 17 May 2019 14:22:03 +0300 Subject: [PATCH 2/3] Refactored the operator logic --- pkg/engine/validation.go | 107 ++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 63 deletions(-) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 40850c4e85..b218823ea4 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -243,69 +243,6 @@ func checkForWildcard(value, pattern string) bool { return wildcard.Match(pattern, value) } -func checkSingleOperator(value float64, operator string) bool { - if operatorVal, err := strconv.ParseFloat(operator, 64); err == nil { - return value == operatorVal - } - - if len(operator) < 2 { - fmt.Printf("Validating error: operator can't have less than 2 characters: %s\n", operator) - return false - } - - if operator[:len(MoreEqual)] == string(MoreEqual) { - operatorVal, err := strconv.ParseFloat(operator[len(MoreEqual):len(operator)], 64) - if err != nil { - fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) - return false - } - - return value >= operatorVal - } - - if operator[:len(LessEqual)] == string(LessEqual) { - operatorVal, err := strconv.ParseFloat(operator[len(LessEqual):len(operator)], 64) - if err != nil { - fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) - return false - } - - return value <= operatorVal - } - - if operator[:len(More)] == string(More) { - operatorVal, err := strconv.ParseFloat(operator[len(More):len(operator)], 64) - if err != nil { - fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) - return false - } - - return value > operatorVal - } - - if operator[:len(Less)] == string(Less) { - operatorVal, err := strconv.ParseFloat(operator[len(Less):len(operator)], 64) - if err != nil { - fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) - return false - } - - return value < operatorVal - } - - if operator[:len(NotEqual)] == string(NotEqual) { - operatorVal, err := strconv.ParseFloat(operator[len(NotEqual):len(operator)], 64) - if err != nil { - fmt.Printf("Validating error: failed to parse operator value: %s\n", operator) - return false - } - - return value != operatorVal - } - - return false -} - func checkForOperator(value float64, pattern string) bool { operators := strings.Split(pattern, "|") @@ -319,6 +256,50 @@ func checkForOperator(value float64, pattern string) bool { return false } +func checkSingleOperator(value float64, pattern string) bool { + if operatorVal, err := strconv.ParseFloat(pattern, 64); err == nil { + return value == operatorVal + } + + if len(pattern) < 2 { + fmt.Printf("Validating error: operator can't have less than 2 characters: %s\n", pattern) + return false + } + + if operatorVal, ok := parseOperator(MoreEqual, pattern); ok { + return value >= operatorVal + } + + if operatorVal, ok := parseOperator(LessEqual, pattern); ok { + return value <= operatorVal + } + + if operatorVal, ok := parseOperator(More, pattern); ok { + return value > operatorVal + } + + if operatorVal, ok := parseOperator(Less, pattern); ok { + return value < operatorVal + } + + if operatorVal, ok := parseOperator(NotEqual, pattern); ok { + return value != operatorVal + } + + fmt.Printf("Validating error: unknown operator: %s\n", pattern) + return false +} + +func parseOperator(operator Operator, pattern string) (float64, bool) { + if pattern[:len(operator)] == string(operator) { + if value, err := strconv.ParseFloat(pattern[len(operator):len(pattern)], 64); err == nil { + return value, true + } + } + + return 0.0, false +} + func wrappedWithParentheses(str string) bool { if len(str) < 2 { return false From 12c5274718b978df487189632932baab2190b6c8 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Fri, 17 May 2019 14:51:54 +0300 Subject: [PATCH 3/3] I have applied notes from review --- pkg/engine/validation.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index b218823ea4..2927fe0c3a 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -73,7 +73,13 @@ func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers } func validateMap(resourcePart, patternPart interface{}) bool { - pattern := patternPart.(map[string]interface{}) + pattern, ok := patternPart.(map[string]interface{}) + + if !ok { + fmt.Printf("Validating error: expected Map, found %T\n", patternPart) + return false + } + resource, ok := resourcePart.(map[string]interface{}) if !ok { @@ -95,7 +101,13 @@ func validateMap(resourcePart, patternPart interface{}) bool { } func validateArray(resourcePart, patternPart interface{}) bool { - patternArray := patternPart.([]interface{}) + patternArray, ok := patternPart.([]interface{}) + + if !ok { + fmt.Printf("Validating error: expected array, found %T\n", patternPart) + return false + } + resourceArray, ok := resourcePart.([]interface{}) if !ok {