diff --git a/pkg/engine/jmespath/functions.go b/pkg/engine/jmespath/functions.go index 1a49c96404..67e13b1c32 100644 --- a/pkg/engine/jmespath/functions.go +++ b/pkg/engine/jmespath/functions.go @@ -38,11 +38,18 @@ var ( regexReplaceAllLiteral = "regex_replace_all_literal" regexMatch = "regex_match" labelMatch = "label_match" + add = "add" + subtract = "subtract" + multiply = "multiply" + divide = "divide" + modulo = "modulo" ) const errorPrefix = "JMESPath function '%s': " const invalidArgumentTypeError = errorPrefix + "%d argument is expected of %s type" const genericError = errorPrefix + "%s" +const zeroDivisionError = errorPrefix + "Zero divisor passed" +const nonIntModuloError = errorPrefix + "Non-integer argument(s) passed for modulo" func getFunctions() []*gojmespath.FunctionEntry { return []*gojmespath.FunctionEntry{ @@ -146,6 +153,46 @@ func getFunctions() []*gojmespath.FunctionEntry { }, Handler: jpLabelMatch, }, + { + Name: add, + Arguments: []ArgSpec{ + {Types: []JpType{JpNumber}}, + {Types: []JpType{JpNumber}}, + }, + Handler: jpAdd, + }, + { + Name: subtract, + Arguments: []ArgSpec{ + {Types: []JpType{JpNumber}}, + {Types: []JpType{JpNumber}}, + }, + Handler: jpSubtract, + }, + { + Name: multiply, + Arguments: []ArgSpec{ + {Types: []JpType{JpNumber}}, + {Types: []JpType{JpNumber}}, + }, + Handler: jpMultiply, + }, + { + Name: divide, + Arguments: []ArgSpec{ + {Types: []JpType{JpNumber}}, + {Types: []JpType{JpNumber}}, + }, + Handler: jpDivide, + }, + { + Name: modulo, + Arguments: []ArgSpec{ + {Types: []JpType{JpNumber}}, + {Types: []JpType{JpNumber}}, + }, + Handler: jpModulo, + }, } } @@ -360,6 +407,100 @@ func jpLabelMatch(arguments []interface{}) (interface{}, error) { return true, nil } +func jpAdd(arguments []interface{}) (interface{}, error) { + var err error + op1, err := validateArg(divide, arguments, 0, reflect.Float64) + if err != nil { + return nil, err + } + + op2, err := validateArg(divide, arguments, 1, reflect.Float64) + if err != nil { + return nil, err + } + + return op1.Float() + op2.Float(), nil +} + +func jpSubtract(arguments []interface{}) (interface{}, error) { + var err error + op1, err := validateArg(divide, arguments, 0, reflect.Float64) + if err != nil { + return nil, err + } + + op2, err := validateArg(divide, arguments, 1, reflect.Float64) + if err != nil { + return nil, err + } + + return op1.Float() - op2.Float(), nil +} + +func jpMultiply(arguments []interface{}) (interface{}, error) { + var err error + op1, err := validateArg(divide, arguments, 0, reflect.Float64) + if err != nil { + return nil, err + } + + op2, err := validateArg(divide, arguments, 1, reflect.Float64) + if err != nil { + return nil, err + } + + return op1.Float() * op2.Float(), nil +} + +func jpDivide(arguments []interface{}) (interface{}, error) { + var err error + op1, err := validateArg(divide, arguments, 0, reflect.Float64) + if err != nil { + return nil, err + } + + op2, err := validateArg(divide, arguments, 1, reflect.Float64) + if err != nil { + return nil, err + } + + if op2.Float() == 0 { + return nil, fmt.Errorf(zeroDivisionError, divide) + } + + return op1.Float() / op2.Float(), nil +} + +func jpModulo(arguments []interface{}) (interface{}, error) { + var err error + op1, err := validateArg(divide, arguments, 0, reflect.Float64) + if err != nil { + return nil, err + } + + op2, err := validateArg(divide, arguments, 1, reflect.Float64) + if err != nil { + return nil, err + } + + val1 := int64(op1.Float()) + val2 := int64(op2.Float()) + + if op1.Float() != float64(val1) { + return nil, fmt.Errorf(nonIntModuloError, modulo) + } + + if op2.Float() != float64(val2) { + return nil, fmt.Errorf(nonIntModuloError, modulo) + } + + if val2 == 0 { + return nil, fmt.Errorf(zeroDivisionError, modulo) + } + + return val1 % val2, nil +} + // InterfaceToString casts an interface to a string type func ifaceToString(iface interface{}) (string, error) { switch iface.(type) { diff --git a/pkg/engine/jmespath/functions_test.go b/pkg/engine/jmespath/functions_test.go index 1b0bf03cca..dac94725a1 100644 --- a/pkg/engine/jmespath/functions_test.go +++ b/pkg/engine/jmespath/functions_test.go @@ -301,3 +301,63 @@ func Test_labelMatch(t *testing.T) { } } + +func TestJMESPathFunctions_Add(t *testing.T) { + jp, err := New("add(`12`, `13`)") + assert.NilError(t, err) + + result, err := jp.Search("") + assert.NilError(t, err) + + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, 25.0) +} + +func TestJMESPathFunctions_Subtract(t *testing.T) { + jp, err := New("subtract(`12`, `7`)") + assert.NilError(t, err) + + result, err := jp.Search("") + assert.NilError(t, err) + + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, 5.0) +} + +func TestJMESPathFunctions_Multiply(t *testing.T) { + jp, err := New("multiply(`3`, `2.5`)") + assert.NilError(t, err) + + result, err := jp.Search("") + assert.NilError(t, err) + + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, 7.5) +} + +func TestJMESPathFunctions_Divide(t *testing.T) { + jp, err := New("divide(`12`, `1.5`)") + assert.NilError(t, err) + + result, err := jp.Search("") + assert.NilError(t, err) + + equal, ok := result.(float64) + assert.Assert(t, ok) + assert.Equal(t, equal, 8.0) +} + +func TestJMESPathFunctions_Modulo(t *testing.T) { + jp, err := New("modulo(`12`, `7`)") + assert.NilError(t, err) + + result, err := jp.Search("") + assert.NilError(t, err) + + equal, ok := result.(int64) + assert.Assert(t, ok) + assert.Equal(t, equal, int64(5)) +} diff --git a/pkg/kyverno/common/regex.go b/pkg/kyverno/common/regex.go index b7e69949d3..e47e0bfcd0 100644 --- a/pkg/kyverno/common/regex.go +++ b/pkg/kyverno/common/regex.go @@ -7,8 +7,8 @@ import ( // RegexVariables represents regex for '{{}}' var RegexVariables = regexp.MustCompile(`\{\{[^{}]*\}\}`) -// AllowedVariables represents regex for {{request.}}, {{serviceAccountName}}, {{serviceAccountNamespace}} and {{@}} -var AllowedVariables = regexp.MustCompile(`\{\{\s*[request\.|serviceAccountName|serviceAccountNamespace|@][^{}]*\}\}`) +// AllowedVariables represents regex for {{request.}}, {{serviceAccountName}}, {{serviceAccountNamespace}}, {{@}} and {{divide(,))}} (#2409) +var AllowedVariables = regexp.MustCompile(`\{\{\s*[request\.|serviceAccountName|serviceAccountNamespace|@|divide][^{}]*\}\}`) // IsHttpRegex represents regex for starts with http:// or https:// var IsHttpRegex = regexp.MustCompile("^(http|https)://")