1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/engine/anchor.go

388 lines
13 KiB
Go
Raw Normal View History

2019-06-13 17:20:00 +03:00
package engine
import (
"fmt"
2019-06-13 17:20:00 +03:00
"strconv"
"github.com/golang/glog"
2019-06-13 17:20:00 +03:00
)
2019-09-25 21:01:45 -07:00
func getAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
if isConditionAnchor(key) || isExistanceAnchor(key) {
anchors[key] = value
continue
}
resources[key] = value
}
return anchors, resources
}
2019-09-25 15:12:33 -07:00
type ValidationHandler interface {
2019-09-25 21:01:45 -07:00
Handle(resourceMap map[string]interface{}, originPattenr interface{}) (string, error)
2019-09-25 15:12:33 -07:00
}
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
switch {
case isConditionAnchor(element):
return NewConditionAnchorHandler(element, pattern, path)
case isExistanceAnchor(element):
return NewExistanceHandler(element, pattern, path)
default:
return NewDefaultHandler(element, pattern, path)
}
}
2019-06-13 17:20:00 +03:00
// CreateAnchorHandler is a factory that create anchor handlers
func CreateAnchorHandler(anchor string, pattern interface{}, path string) ValidationAnchorHandler {
switch {
case isConditionAnchor(anchor):
return NewConditionAnchorValidationHandler(anchor, pattern, path)
case isExistanceAnchor(anchor):
return NewExistanceAnchorValidationHandler(anchor, pattern, path)
default:
return NewNoAnchorValidationHandler(path)
}
}
2019-09-25 15:12:33 -07:00
func NewDefaultHandler(element string, pattern interface{}, path string) ValidationHandler {
return DefaultHandler{
element: element,
pattern: pattern,
path: path,
}
}
type DefaultHandler struct {
element string
pattern interface{}
path string
}
2019-09-25 21:01:45 -07:00
func (dh DefaultHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
2019-09-25 15:12:33 -07:00
currentPath := dh.path + dh.element + "/"
if dh.pattern == "*" && resourceMap[dh.element] != nil {
2019-09-25 21:01:45 -07:00
return "", nil
2019-09-25 15:12:33 -07:00
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
2019-09-25 21:01:45 -07:00
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
2019-09-25 15:12:33 -07:00
} else {
path, err := validateResourceElement(resourceMap[dh.element], dh.pattern, originPattern, currentPath)
if err != nil {
2019-09-25 21:01:45 -07:00
return path, err
2019-09-25 15:12:33 -07:00
}
}
2019-09-25 21:01:45 -07:00
return "", nil
2019-09-25 15:12:33 -07:00
}
func NewConditionAnchorHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return ConditionAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
type ConditionAnchorHandler struct {
anchor string
pattern interface{}
path string
}
2019-09-25 21:01:45 -07:00
func (ch ConditionAnchorHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
anchorKey := removeAnchor(ch.anchor)
currentPath := ch.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
if err != nil {
return returnPath, err
2019-09-25 15:12:33 -07:00
}
2019-09-25 21:01:45 -07:00
return "", nil
2019-09-25 15:12:33 -07:00
}
2019-09-25 21:01:45 -07:00
return "", nil
2019-09-25 15:12:33 -07:00
2019-09-25 21:01:45 -07:00
// return false
// var value interface{}
// var currentPath string
// var ok bool
// // check for anchor condition
// anchorSatisfied := func() bool {
// anchorKey := removeAnchor(ch.anchor)
// currentPath = ch.path + anchorKey + "/"
// // check if anchor is present in resource
// if value, ok = resourceMap[anchorKey]; ok {
// // validate the values of the pattern
// _, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
// if err == nil {
// return true
// }
// // return ValidateValueWithPattern(value, ch.pattern)
// }
// return false
// }()
// if !anchorSatisfied {
// return "", nil
// }
// path, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
// if err != nil {
// return path, err
// }
2019-09-25 15:12:33 -07:00
// evauluate the anchor and resource values
// for key, element := range resourceMap {
// currentPath := ch.path + key + "/"
// if !ValidateValueWithPattern(element, ch.pattern) {
// // the anchor does not match so ignore
// continue
// }
// path, err := validateResourceElement(element, ch.pattern, originPattern, currentPath)
// if err != nil {
// return path, err
// }
// }
2019-09-25 21:01:45 -07:00
return "", nil
2019-09-25 15:12:33 -07:00
}
func NewExistanceHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return ExistanceHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
type ExistanceHandler struct {
anchor string
pattern interface{}
path string
}
2019-09-25 21:01:45 -07:00
func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
2019-09-25 15:12:33 -07:00
// skip is used by existance anchor to not process further if condition is not satisfied
2019-09-25 21:01:45 -07:00
anchorKey := removeAnchor(eh.anchor)
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// Existance anchor can only exist on resource value type of list
switch typedResource := value.(type) {
case []interface{}:
typedPattern, ok := eh.pattern.([]interface{})
if !ok {
return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of lis to compare against resource", eh.pattern)
}
// get the first item in the pattern array
patternMap := typedPattern[0]
typedPatternMap, ok := patternMap.(map[string]interface{})
if !ok {
return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
2019-09-25 16:06:37 -07:00
}
2019-09-25 21:01:45 -07:00
return validateExistenceListResource(typedResource, typedPatternMap, originPattern, currentPath)
default:
glog.Error("Invalid type: Existance ^ () anchor can be used only on list/array type resource")
return currentPath, fmt.Errorf("Invalid resource type %T: Existance ^ () anchor can be used only on list/array type resource", value)
2019-09-25 15:12:33 -07:00
}
2019-09-25 21:01:45 -07:00
_, err := validateResourceElement(value, eh.pattern, originPattern, currentPath)
if err == nil {
// if the anchor value is the satisfied then we evaluate the next
return "", nil
}
// return ValidateValueWithPattern(value, eh.pattern)
2019-09-25 15:12:33 -07:00
}
// anchoredEntries++
2019-09-25 16:06:37 -07:00
// path, err := validateResourceElement(value, eh.pattern, originPattern, currentPath)
// if err != nil {
// return path, false, err
// }
2019-09-25 15:12:33 -07:00
// if anchoredEntries == 0 {
// return eh.path, fmt.Errorf("Existance anchor %s used, but no suitable entries were found", eh.anchor)
// }
2019-09-25 21:01:45 -07:00
return "", nil
2019-09-25 15:12:33 -07:00
// anchoredEntries := 0
// for key, element := range resourceMap {
// currentPath := eh.path + key + "/"
// // check for anchor condition
// if !ValidateValueWithPattern(element, eh.pattern) {
// // the anchor does not match so ignore
// continue
// }
// anchoredEntries++
// path, err := validateResourceElement(element, eh.pattern, originPattern, currentPath)
// if err != nil {
// return path, err
// }
// }
// if anchoredEntries == 0 {
// return eh.path, fmt.Errorf("Existance anchor %s used, but no suitable entries were found", eh.anchor)
// }
// return "", nil
}
2019-09-25 21:01:45 -07:00
func validateExistenceListResource(resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
// the idea is atleast on the elements in the array should satisfy the pattern
// if non satisfy then throw an error
for i, resourceElement := range resourceList {
currentPath := path + strconv.Itoa(i) + "/"
_, err := validateResourceElement(resourceElement, patternMap, originPattern, currentPath)
if err == nil {
// condition is satisfied, dont check further
glog.V(4).Infof("Existence check satisfied at path %s, for pattern %v", currentPath, patternMap)
return "", nil
}
}
// none of the existance checks worked, so thats a failure sceanario
return path, fmt.Errorf("Existence anchor validation failed at path %s", path)
}
2019-06-13 17:20:00 +03:00
// ValidationAnchorHandler is an interface that represents
// a family of anchor handlers for array of maps
// resourcePart must be an array of dictionaries
// patternPart must be a dictionary with anchors
type ValidationAnchorHandler interface {
Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) (string, error)
2019-06-13 17:20:00 +03:00
}
// NoAnchorValidationHandler just calls validateMap
// because no anchors were found in the pattern map
type NoAnchorValidationHandler struct {
path string
}
// NewNoAnchorValidationHandler creates new instance of
// NoAnchorValidationHandler
func NewNoAnchorValidationHandler(path string) ValidationAnchorHandler {
return &NoAnchorValidationHandler{
path: path,
}
}
// Handle performs validation in context of NoAnchorValidationHandler
func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) (string, error) {
2019-06-13 17:20:00 +03:00
for i, resourceElement := range resourcePart {
currentPath := navh.path + strconv.Itoa(i) + "/"
typedResourceElement, ok := resourceElement.(map[string]interface{})
if !ok {
return currentPath, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
2019-06-13 17:20:00 +03:00
}
path, err := validateMap(typedResourceElement, patternPart, originPattern, currentPath)
if err != nil {
return path, err
}
2019-06-13 17:20:00 +03:00
}
return "", nil
2019-06-13 17:20:00 +03:00
}
// ConditionAnchorValidationHandler performs
// validation only for array elements that
// pass condition in the anchor
// (key): value
type ConditionAnchorValidationHandler struct {
anchor string
pattern interface{}
path string
}
// NewConditionAnchorValidationHandler creates new instance of
// NoAnchorValidationHandler
func NewConditionAnchorValidationHandler(anchor string, pattern interface{}, path string) ValidationAnchorHandler {
return &ConditionAnchorValidationHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle performs validation in context of ConditionAnchorValidationHandler
func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) (string, error) {
_, path, handlingResult := handleConditionCases(resourcePart, patternPart, cavh.anchor, cavh.pattern, cavh.path, originPattern)
2019-06-13 17:20:00 +03:00
return path, handlingResult
2019-06-13 17:20:00 +03:00
}
// ExistanceAnchorValidationHandler performs
// validation only for array elements that
// pass condition in the anchor
// AND requires an existance of at least one
// element that passes this condition
// ^(key): value
type ExistanceAnchorValidationHandler struct {
anchor string
pattern interface{}
path string
}
// NewExistanceAnchorValidationHandler creates new instance of
// NoAnchorValidationHandler
func NewExistanceAnchorValidationHandler(anchor string, pattern interface{}, path string) ValidationAnchorHandler {
return &ExistanceAnchorValidationHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle performs validation in context of ExistanceAnchorValidationHandler
func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) (string, error) {
anchoredEntries, path, err := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path, originPattern)
if err != nil {
return path, err
}
2019-06-18 13:07:55 +03:00
if 0 == anchoredEntries {
return path, fmt.Errorf("Existance anchor %s used, but no suitable entries were found", eavh.anchor)
2019-06-13 17:20:00 +03:00
}
return "", nil
2019-06-13 17:20:00 +03:00
}
// check if array element fits the anchor
func checkForAnchorCondition(anchor string, pattern interface{}, resourceMap map[string]interface{}) bool {
anchorKey := removeAnchor(anchor)
if value, ok := resourceMap[anchorKey]; ok {
return ValidateValueWithPattern(value, pattern)
}
return false
}
// both () and ^() are checking conditions and have a lot of similar logic
// the only difference is that ^() requires existace of one element
2019-06-18 13:07:55 +03:00
// anchoredEntries var counts this occurences.
func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string, originPattern interface{}) (int, string, error) {
2019-06-18 13:07:55 +03:00
anchoredEntries := 0
2019-06-13 17:20:00 +03:00
for i, resourceElement := range resourcePart {
currentPath := path + strconv.Itoa(i) + "/"
typedResourceElement, ok := resourceElement.(map[string]interface{})
if !ok {
glog.V(4).Infof("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
return 0, currentPath, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
2019-06-13 17:20:00 +03:00
}
if !checkForAnchorCondition(anchor, pattern, typedResourceElement) {
continue
}
2019-06-18 13:07:55 +03:00
anchoredEntries++
path, err := validateMap(typedResourceElement, patternPart, originPattern, currentPath)
if err != nil {
return 0, path, err
}
2019-06-13 17:20:00 +03:00
}
return anchoredEntries, "", nil
2019-06-13 17:20:00 +03:00
}