Skip to content

Commit

Permalink
Implemented validation across same yaml
Browse files Browse the repository at this point in the history
  • Loading branch information
belyshevdenis committed Jun 20, 2019
1 parent f6ebb05 commit 46cbbf9
Show file tree
Hide file tree
Showing 6 changed files with 732 additions and 33 deletions.
6 changes: 6 additions & 0 deletions .directory
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[Dolphin]
Timestamp=2019,6,20,15,39,37
Version=3

[Settings]
HiddenFilesShown=true
18 changes: 9 additions & 9 deletions pkg/engine/anchor.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func CreateAnchorHandler(anchor string, pattern interface{}, path string) Valida
// resourcePart must be an array of dictionaries
// patternPart must be a dictionary with anchors
type ValidationAnchorHandler interface {
Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult
Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult
}

// NoAnchorValidationHandler just calls validateMap
Expand All @@ -41,7 +41,7 @@ func NewNoAnchorValidationHandler(path string) ValidationAnchorHandler {
}

// Handle performs validation in context of NoAnchorValidationHandler
func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult {
func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult {
handlingResult := result.NewRuleApplicationResult("")

for i, resourceElement := range resourcePart {
Expand All @@ -53,7 +53,7 @@ func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patter
return handlingResult
}

res := validateMap(typedResourceElement, patternPart, currentPath)
res := validateMap(typedResourceElement, patternPart, originPattern, currentPath)
handlingResult.MergeWith(&res)
}

Expand Down Expand Up @@ -81,8 +81,8 @@ func NewConditionAnchorValidationHandler(anchor string, pattern interface{}, pat
}

// Handle performs validation in context of ConditionAnchorValidationHandler
func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult {
_, handlingResult := handleConditionCases(resourcePart, patternPart, cavh.anchor, cavh.pattern, cavh.path)
func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult {
_, handlingResult := handleConditionCases(resourcePart, patternPart, cavh.anchor, cavh.pattern, cavh.path, originPattern)

return handlingResult
}
Expand Down Expand Up @@ -110,8 +110,8 @@ func NewExistanceAnchorValidationHandler(anchor string, pattern interface{}, pat
}

// Handle performs validation in context of ExistanceAnchorValidationHandler
func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult {
anchoredEntries, handlingResult := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path)
func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult {
anchoredEntries, handlingResult := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path, originPattern)

if 0 == anchoredEntries {
handlingResult.FailWithMessagef("Existance anchor %s used, but no suitable entries were found", eavh.anchor)
Expand All @@ -134,7 +134,7 @@ func checkForAnchorCondition(anchor string, pattern interface{}, resourceMap map
// both () and ^() are checking conditions and have a lot of similar logic
// the only difference is that ^() requires existace of one element
// anchoredEntries var counts this occurences.
func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string) (int, result.RuleApplicationResult) {
func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string, originPattern interface{}) (int, result.RuleApplicationResult) {
handlingResult := result.NewRuleApplicationResult("")
anchoredEntries := 0

Expand All @@ -152,7 +152,7 @@ func handleConditionCases(resourcePart []interface{}, patternPart map[string]int
}

anchoredEntries++
res := validateMap(typedResourceElement, patternPart, currentPath)
res := validateMap(typedResourceElement, patternPart, originPattern, currentPath)
handlingResult.MergeWith(&res)
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/engine/pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const (
Less Operator = "<"
)

const relativePrefix Operator = "./"
const referenceSign Operator = "$()"

// ValidateValueWithPattern validates value with operators and wildcards
func ValidateValueWithPattern(value, pattern interface{}) bool {
switch typedPattern := pattern.(type) {
Expand Down
22 changes: 22 additions & 0 deletions pkg/engine/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,28 @@ func isConditionAnchor(str string) bool {
return (str[0] == '(' && str[len(str)-1] == ')')
}

func getRawKeyIfWrappedWithAttributes(str string) string {
if len(str) < 2 {
return str
}

if str[0] == '(' && str[len(str)-1] == ')' {
return str[1 : len(str)-1]
} else if (str[0] == '$' || str[0] == '^' || str[0] == '+') && (str[1] == '(' && str[len(str)-1] == ')') {
return str[2 : len(str)-1]
} else {
return str
}
}

func isStringIsReference(str string) bool {
if len(str) < len(referenceSign) {
return false
}

return str[0] == '$' && str[1] == '(' && str[len(str)-1] == ')'
}

func isExistanceAnchor(str string) bool {
left := "^("
right := ")"
Expand Down
153 changes: 139 additions & 14 deletions pkg/engine/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package engine

import (
"encoding/json"
"fmt"
"path/filepath"
"reflect"
"strconv"
"strings"

kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/result"
Expand Down Expand Up @@ -48,13 +52,13 @@ func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers
// 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{}) result.RuleApplicationResult {
return validateResourceElement(resource, pattern, "/")
return validateResourceElement(resource, pattern, pattern, "/")
}

// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
// and calls corresponding handler
// Pattern tree and resource tree can have different structure. In this case validation fails
func validateResourceElement(resourceElement, patternElement interface{}, path string) result.RuleApplicationResult {
func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) result.RuleApplicationResult {
res := result.NewRuleApplicationResult("")
// TODO: Move similar message templates to message package

Expand All @@ -67,7 +71,7 @@ func validateResourceElement(resourceElement, patternElement interface{}, path s
return res
}

return validateMap(typedResourceElement, typedPatternElement, path)
return validateMap(typedResourceElement, typedPatternElement, originPattern, path)
// array
case []interface{}:
typedResourceElement, ok := resourceElement.([]interface{})
Expand All @@ -76,9 +80,18 @@ func validateResourceElement(resourceElement, patternElement interface{}, path s
return res
}

return validateArray(typedResourceElement, typedPatternElement, path)
return validateArray(typedResourceElement, typedPatternElement, originPattern, path)
// elementary values
case string, float64, int, int64, bool, nil:
/*Analyze pattern */
if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() == reflect.String {
if isStringIsReference(checkedPattern.String()) { //check for $ anchor
patternElement, res = actualizePattern(originPattern, checkedPattern.String(), path)
if result.Failed == res.Reason {
return res
}
}
}
if !ValidateValueWithPattern(resourceElement, patternElement) {
res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", resourceElement, patternElement, path)
}
Expand All @@ -92,7 +105,7 @@ func validateResourceElement(resourceElement, patternElement interface{}, path s

// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
func validateMap(resourceMap, patternMap map[string]interface{}, path string) result.RuleApplicationResult {
func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) result.RuleApplicationResult {
res := result.NewRuleApplicationResult("")

for key, patternElement := range patternMap {
Expand All @@ -104,18 +117,15 @@ func validateMap(resourceMap, patternMap map[string]interface{}, path string) re
} else if patternElement == "*" && resourceMap[key] == nil {
res.FailWithMessagef("Field %s is not present", key)
} else {
elementResult := validateResourceElement(resourceMap[key], patternElement, path+key+"/")
elementResult := validateResourceElement(resourceMap[key], patternElement, origPattern, path+key+"/")
res.MergeWith(&elementResult)
}
}

return res
}

// If validateResourceElement detects array element inside resource and pattern trees, it goes to validateArray
// Unlike the validateMap, we should check the array elements type on-site, because in case of maps, we should
// get anchors and check each array element with it.
func validateArray(resourceArray, patternArray []interface{}, path string) result.RuleApplicationResult {
func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) result.RuleApplicationResult {
res := result.NewRuleApplicationResult("")

if 0 == len(patternArray) {
Expand All @@ -126,25 +136,140 @@ func validateArray(resourceArray, patternArray []interface{}, path string) resul
case map[string]interface{}:
// This is special case, because maps in arrays can have anchors that must be
// processed with the special way affecting the entire array
arrayResult := validateArrayOfMaps(resourceArray, typedPatternElement, path)
arrayResult := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path)
res.MergeWith(&arrayResult)
default:
// In all other cases - detect type and handle each array element with validateResourceElement
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
elementResult := validateResourceElement(resourceArray[i], patternElement, currentPath)
elementResult := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath)
res.MergeWith(&elementResult)
}
}

return res
}

func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, result.RuleApplicationResult) {
res := result.NewRuleApplicationResult("")
var foundValue interface{}

referencePattern = strings.Trim(referencePattern, "$()")

operator := getOperatorFromStringPattern(referencePattern)
referencePattern = referencePattern[len(operator):]

if len(referencePattern) == 0 {
res.FailWithMessagef("Expected path. Found empty reference")
return nil, res
}

actualPath := FormAbsolutePath(referencePattern, absolutePath)

valFromReference, res := getValueFromReference(origPattern, actualPath)

if result.Failed == res.Reason {
return nil, res
}

if operator == Equal { //if operator does not exist return raw value
return valFromReference, res
}

foundValue, res = valFromReferenceToString(valFromReference, string(operator))

return string(operator) + foundValue.(string), res
}

//Parse value to string
func valFromReferenceToString(value interface{}, operator string) (string, result.RuleApplicationResult) {
res := result.NewRuleApplicationResult("")

switch typed := value.(type) {
case string:
return typed, res
case int, int64:
return fmt.Sprintf("%d", value), res
case float64:
return fmt.Sprintf("%f", value), res
default:
res.FailWithMessagef("Incorrect expression. Operator %s does not match with value: %v", operator, value)
return "", res
}
}

func FormAbsolutePath(referencePath, absolutePath string) string {
if filepath.IsAbs(referencePath) {
return referencePath
}

return filepath.Join(absolutePath, referencePath)
}

//Prepares original pattern, path to value, and call traverse function
func getValueFromReference(origPattern interface{}, reference string) (interface{}, result.RuleApplicationResult) {
originalPatternMap := origPattern.(map[string]interface{})
reference = reference[1:len(reference)]
statements := strings.Split(reference, "/")

return getValueFromPattern(originalPatternMap, statements, 0)
}

func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, result.RuleApplicationResult) {
res := result.NewRuleApplicationResult("")

for key, pattern := range patternMap {
rawKey := getRawKeyIfWrappedWithAttributes(key)

if rawKey == keys[len(keys)-1] && currentKeyIndex == len(keys)-1 {
return pattern, res
} else if rawKey != keys[currentKeyIndex] && currentKeyIndex != len(keys)-1 {
continue
}

switch typedPattern := pattern.(type) {
case []interface{}:
if keys[currentKeyIndex] == rawKey {
for i, value := range typedPattern {
resourceMap, ok := value.(map[string]interface{})
if !ok {
res.FailWithMessagef("Pattern and resource have different structures. Expected %T, found %T", pattern, value)
return nil, res
}
if keys[currentKeyIndex+1] == strconv.Itoa(i) {
return getValueFromPattern(resourceMap, keys, currentKeyIndex+2)
}
res.FailWithMessagef("Reference to non-existent place in the document")
}
}
res.FailWithMessagef("Reference to non-existent place in the document")
case map[string]interface{}:
if keys[currentKeyIndex] == rawKey {
return getValueFromPattern(typedPattern, keys, currentKeyIndex+1)
}
res.FailWithMessagef("Reference to non-existent place in the document")
case string, float64, int, int64, bool, nil:
continue
}
}

path := ""

/*for i := len(keys) - 1; i >= 0; i-- {
path = keys[i] + path + "/"
}*/
for _, elem := range keys {
path = "/" + elem + path
}
res.FailWithMessagef("No value found for specified reference: %s", path)
return nil, res
}

// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
// and then validates each map due to the pattern
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, path string) result.RuleApplicationResult {
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) result.RuleApplicationResult {
anchor, pattern := getAnchorFromMap(patternMap)

handler := CreateAnchorHandler(anchor, pattern, path)
return handler.Handle(resourceMapArray, patternMap)
return handler.Handle(resourceMapArray, patternMap, originPattern)
}
Loading

0 comments on commit 46cbbf9

Please sign in to comment.