1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 10:04:25 +00:00

resolved conditional anchor issue and added validation to pattern labels ()

* resolved conditional anchor issue and added validation to pattern labels

* restored IsConditionAnchor

* added annotation and anypattern validation

* added conditional anchor key checker

* reverted docs

* fixed tests

* modified validation

* modified validate condition check
This commit is contained in:
Mohan B E 2020-08-29 06:52:22 +05:30 committed by GitHub
parent b067f41d02
commit a827f88dc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 297 additions and 92 deletions

View file

@ -121,10 +121,10 @@ For conditional anchors, the child element is considered to be part of the "if"
pattern:
metadata:
labels:
allow-docker: true
allow-docker: "true"
spec:
(volumes):
(hostPath):
- (hostPath):
path: "/var/run/docker.sock"
````

View file

@ -5,26 +5,28 @@ import (
"strconv"
"github.com/go-logr/logr"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
"github.com/nirmata/kyverno/pkg/engine/common"
"sigs.k8s.io/controller-runtime/pkg/log"
)
//ValidationHandler for element processes
type ValidationHandler interface {
Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error)
Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *common.AnchorKey) (string, error)
}
type resourceElementHandler = func(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string) (string, error)
type resourceElementHandler = func(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string, ac *common.AnchorKey) (string, error)
//CreateElementHandler factory to process elements
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
switch {
case IsConditionAnchor(element):
case commonAnchors.IsConditionAnchor(element):
return NewConditionAnchorHandler(element, pattern, path)
case IsExistenceAnchor(element):
case commonAnchors.IsExistenceAnchor(element):
return NewExistenceHandler(element, pattern, path)
case IsEqualityAnchor(element):
case commonAnchors.IsEqualityAnchor(element):
return NewEqualityHandler(element, pattern, path)
case IsNegationAnchor(element):
case commonAnchors.IsNegationAnchor(element):
return NewNegationHandler(element, pattern, path)
default:
return NewDefaultHandler(element, pattern, path)
@ -48,8 +50,8 @@ type NegationHandler struct {
}
//Handle process negation handler
func (nh NegationHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
anchorKey := removeAnchor(nh.anchor)
func (nh NegationHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *common.AnchorKey) (string, error) {
anchorKey := commonAnchors.RemoveAnchor(nh.anchor)
currentPath := nh.path + anchorKey + "/"
// if anchor is present in the resource then fail
if _, ok := resourceMap[anchorKey]; ok {
@ -77,13 +79,13 @@ type EqualityHandler struct {
}
//Handle processed condition anchor
func (eh EqualityHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
anchorKey := removeAnchor(eh.anchor)
func (eh EqualityHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *common.AnchorKey) (string, error) {
anchorKey := commonAnchors.RemoveAnchor(eh.anchor)
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(log.Log, value, eh.pattern, originPattern, currentPath)
returnPath, err := handler(log.Log, value, eh.pattern, originPattern, currentPath, ac)
if err != nil {
return returnPath, err
}
@ -109,14 +111,14 @@ type DefaultHandler struct {
}
//Handle process non anchor element
func (dh DefaultHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
func (dh DefaultHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *common.AnchorKey) (string, error) {
currentPath := dh.path + dh.element + "/"
if dh.pattern == "*" && resourceMap[dh.element] != nil {
return "", nil
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
} else {
path, err := handler(log.Log, resourceMap[dh.element], dh.pattern, originPattern, currentPath)
path, err := handler(log.Log, resourceMap[dh.element], dh.pattern, originPattern, currentPath, ac)
if err != nil {
return path, err
}
@ -141,13 +143,13 @@ type ConditionAnchorHandler struct {
}
//Handle processed condition anchor
func (ch ConditionAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
anchorKey := removeAnchor(ch.anchor)
func (ch ConditionAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *common.AnchorKey) (string, error) {
anchorKey := commonAnchors.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 := handler(log.Log, value, ch.pattern, originPattern, currentPath)
returnPath, err := handler(log.Log, value, ch.pattern, originPattern, currentPath, ac)
if err != nil {
return returnPath, err
}
@ -174,9 +176,9 @@ type ExistenceHandler struct {
}
//Handle processes the existence anchor handler
func (eh ExistenceHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
func (eh ExistenceHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *common.AnchorKey) (string, error) {
// skip is used by existence anchor to not process further if condition is not satisfied
anchorKey := removeAnchor(eh.anchor)
anchorKey := commonAnchors.RemoveAnchor(eh.anchor)
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
@ -193,7 +195,7 @@ func (eh ExistenceHandler) Handle(handler resourceElementHandler, resourceMap ma
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)
}
return validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath)
return validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath, ac)
default:
return currentPath, fmt.Errorf("Invalid resource type %T: Existence ^ () anchor can be used only on list/array type resource", value)
}
@ -201,12 +203,12 @@ func (eh ExistenceHandler) Handle(handler resourceElementHandler, resourceMap ma
return "", nil
}
func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *common.AnchorKey) (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 := handler(log.Log, resourceElement, patternMap, originPattern, currentPath)
_, err := handler(log.Log, resourceElement, patternMap, originPattern, currentPath, ac)
if err == nil {
// condition is satisfied, dont check further
return "", nil
@ -221,7 +223,7 @@ func GetAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]i
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
if IsConditionAnchor(key) || IsExistenceAnchor(key) || IsEqualityAnchor(key) || IsNegationAnchor(key) {
if commonAnchors.IsConditionAnchor(key) || commonAnchors.IsExistenceAnchor(key) || commonAnchors.IsEqualityAnchor(key) || commonAnchors.IsNegationAnchor(key) {
anchors[key] = value
continue
}

View file

@ -1,4 +1,4 @@
package anchor
package common
// IsAnchor is a function handler
type IsAnchor func(str string) bool
@ -58,7 +58,7 @@ func IsExistenceAnchor(str string) bool {
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
func removeAnchor(key string) string {
func RemoveAnchor(key string) string {
if IsConditionAnchor(key) {
return key[1 : len(key)-1]
}

View file

@ -1,4 +1,4 @@
package anchor
package common
import (
"testing"

View file

@ -0,0 +1,73 @@
package common
import (
"github.com/nirmata/kyverno/pkg/engine/anchor/common"
)
// AnchorKey - contains map of anchors
type AnchorKey struct {
// anchorMap - for each anchor key in the patterns it will maintains information if the key exists in the resource
// if anchor key of the pattern exists in the resource then (key)=true else (key)=false
anchorMap map[string]bool
// AnchorError - used in validate to break execution of the recursion when if condition fails
AnchorError error
}
// NewAnchorMap -initialize anchorMap
func NewAnchorMap() *AnchorKey {
return &AnchorKey{anchorMap: make(map[string]bool)}
}
// IsAnchorError - if any of the anchor key doesn't exists in the resource then it will return true
// if any of (key)=false then return IsAnchorError() as true
// if all the keys exists in the pattern exists in resource then return IsAnchorError() as false
func (ac *AnchorKey) IsAnchorError() bool {
for _, v := range ac.anchorMap {
if v == false {
return true
}
}
return false
}
// CheckAnchorInResource
// Check if condition anchor key has values
func (ac *AnchorKey) CheckAnchorInResource(pattern interface{}, resource interface{}) {
switch typed := pattern.(type) {
case map[string]interface{}:
for key := range typed {
if common.IsConditionAnchor(key) || common.IsExistenceAnchor(key) || common.IsNegationAnchor(key) {
val, ok := ac.anchorMap[key]
if !ok {
ac.anchorMap[key] = false
} else if ok && val == true {
continue
}
if doesAnchorsKeyHasValue(key, resource) {
ac.anchorMap[key] = true
}
}
}
}
}
// Checks if anchor key has value in resource
func doesAnchorsKeyHasValue(key string, resource interface{}) bool {
akey := common.RemoveAnchor(key)
switch typed := resource.(type) {
case map[string]interface{}:
if _, ok := typed[akey]; ok {
return true
}
return false
case []interface{}:
for _, value := range typed {
if doesAnchorsKeyHasValue(key, value) {
return true
}
}
return false
default:
return false
}
}

View file

@ -15,7 +15,7 @@ import (
jsonpatch "github.com/evanphx/json-patch"
"github.com/go-logr/logr"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/utils"
)
@ -192,7 +192,7 @@ func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path stri
for key, value := range overlayMap {
// skip anchor element because it has condition, not
// the value that must replace resource value
if anchor.IsConditionAnchor(key) {
if commonAnchors.IsConditionAnchor(key) {
continue
}
@ -200,7 +200,7 @@ func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path stri
currentPath := path + noAnchorKey + "/"
resourcePart, ok := resourceMap[noAnchorKey]
if ok && !anchor.IsAddingAnchor(key) {
if ok && !commonAnchors.IsAddingAnchor(key) {
// Key exists - go down through the overlay and resource trees
patches, err := applyOverlay(resourcePart, value, currentPath)
if err != nil {

View file

@ -6,7 +6,7 @@ import (
"strconv"
"github.com/go-logr/logr"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
"github.com/nirmata/kyverno/pkg/engine/validate"
"sigs.k8s.io/controller-runtime/pkg/log"
)
@ -85,7 +85,7 @@ func checkConditionOnArray(resource, overlay []interface{}, path string) (string
func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}, path string) (string, overlayError) {
for key, overlayValue := range anchors {
// skip if key does not have condition anchor
if !anchor.IsConditionAnchor(key) {
if !commonAnchors.IsConditionAnchor(key) {
continue
}

View file

@ -3,7 +3,7 @@ package mutate
import (
"bytes"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
)
type buffer struct {
@ -22,11 +22,11 @@ func (buff buffer) MarshalJSON() ([]byte, error) {
// removeAnchor remove special characters around anchored key
func removeAnchor(key string) string {
if anchor.IsConditionAnchor(key) {
if commonAnchors.IsConditionAnchor(key) {
return key[1 : len(key)-1]
}
if anchor.IsExistenceAnchor(key) || anchor.IsAddingAnchor(key) || anchor.IsEqualityAnchor(key) || anchor.IsNegationAnchor(key) {
if commonAnchors.IsExistenceAnchor(key) || commonAnchors.IsAddingAnchor(key) || commonAnchors.IsEqualityAnchor(key) || commonAnchors.IsNegationAnchor(key) {
return key[2 : len(key)-1]
}
@ -52,9 +52,9 @@ func getAnchorAndElementsFromMap(anchorsMap map[string]interface{}) (map[string]
anchors := make(map[string]interface{})
elementsWithoutanchor := make(map[string]interface{})
for key, value := range anchorsMap {
if anchor.IsConditionAnchor(key) {
if commonAnchors.IsConditionAnchor(key) {
anchors[key] = value
} else if !anchor.IsAddingAnchor(key) {
} else if !commonAnchors.IsAddingAnchor(key) {
elementsWithoutanchor[key] = value
}
}

View file

@ -5,7 +5,7 @@ import (
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchor "github.com/nirmata/kyverno/pkg/engine/anchor/common"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/log"
)
@ -115,7 +115,7 @@ func GetAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{}
result := make(map[string]interface{})
for key, value := range anchorsMap {
if anchor.IsConditionAnchor(key) {
if commonAnchor.IsConditionAnchor(key) {
result[key] = value
}
}

View file

@ -0,0 +1,54 @@
package validate
import (
"container/list"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
)
// Checks if pattern has anchors
func hasNestedAnchors(pattern interface{}) bool {
switch typed := pattern.(type) {
case map[string]interface{}:
if anchors := getAnchorsFromMap(typed); len(anchors) > 0 {
return true
}
for _, value := range typed {
if hasNestedAnchors(value) {
return true
}
}
return false
case []interface{}:
for _, value := range typed {
if hasNestedAnchors(value) {
return true
}
}
return false
default:
return false
}
}
// getSortedNestedAnchorResource - sorts anchors key
func getSortedNestedAnchorResource(resources map[string]interface{}) *list.List {
sortedResourceKeys := list.New()
for k, v := range resources {
if hasNestedAnchors(v) {
sortedResourceKeys.PushFront(k)
}
sortedResourceKeys.PushBack(k)
}
return sortedResourceKeys
}
// getAnchorsFromMap gets the anchor map
func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for key, value := range anchorsMap {
if commonAnchors.IsConditionAnchor(key) || commonAnchors.IsExistenceAnchor(key) || commonAnchors.IsEqualityAnchor(key) || commonAnchors.IsNegationAnchor(key) {
result[key] = value
}
}
return result
}

View file

@ -10,15 +10,21 @@ import (
"github.com/go-logr/logr"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
"github.com/nirmata/kyverno/pkg/engine/common"
"github.com/nirmata/kyverno/pkg/engine/operator"
)
// ValidateResourceWithPattern is a start of element-by-element validation process
// It assumes that validation is started from root, so "/" is passed
func ValidateResourceWithPattern(log logr.Logger, resource, pattern interface{}) (string, error) {
path, err := validateResourceElement(log, resource, pattern, pattern, "/")
// newAnchorMap - to check anchor key has values
ac := common.NewAnchorMap()
path, err := validateResourceElement(log, resource, pattern, pattern, "/", ac)
if err != nil {
return path, err
if !ac.IsAnchorError() {
return path, err
}
}
return "", nil
@ -27,7 +33,7 @@ func ValidateResourceWithPattern(log logr.Logger, resource, pattern interface{})
// 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(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string) (string, error) {
func validateResourceElement(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string, ac *common.AnchorKey) (string, error) {
var err error
switch typedPatternElement := patternElement.(type) {
// map
@ -37,8 +43,9 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
log.V(4).Info("Pattern and resource have different structures.", "path", path, "expected", fmt.Sprintf("%T", patternElement), "current", fmt.Sprintf("%T", resourceElement))
return path, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
}
return validateMap(log, typedResourceElement, typedPatternElement, originPattern, path)
// CheckAnchorInResource - check anchor anchor key exists in resource and update the AnchorKey fields.
ac.CheckAnchorInResource(typedPatternElement, typedResourceElement)
return validateMap(log, typedResourceElement, typedPatternElement, originPattern, path, ac)
// array
case []interface{}:
typedResourceElement, ok := resourceElement.([]interface{})
@ -46,8 +53,7 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
log.V(4).Info("Pattern and resource have different structures.", "path", path, "expected", fmt.Sprintf("%T", patternElement), "current", fmt.Sprintf("%T", resourceElement))
return path, fmt.Errorf("Validation rule Failed at path %s, resource does not satisfy the expected overlay pattern", path)
}
return validateArray(log, typedResourceElement, typedPatternElement, originPattern, path)
return validateArray(log, typedResourceElement, typedPatternElement, originPattern, path, ac)
// elementary values
case string, float64, int, int64, bool, nil:
/*Analyze pattern */
@ -72,7 +78,7 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
// 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(log logr.Logger, resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) {
func validateMap(log logr.Logger, resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string, ac *common.AnchorKey) (string, error) {
// check if there is anchor in pattern
// Phase 1 : Evaluate all the anchors
// Phase 2 : Evaluate non-anchors
@ -85,23 +91,31 @@ func validateMap(log logr.Logger, resourceMap, patternMap map[string]interface{}
// - Existence
// - Equality
handler := anchor.CreateElementHandler(key, patternElement, path)
handlerPath, err := handler.Handle(validateResourceElement, resourceMap, origPattern)
handlerPath, err := handler.Handle(validateResourceElement, resourceMap, origPattern, ac)
// if there are resource values at same level, then anchor acts as conditional instead of a strict check
// but if there are non then its a if then check
if err != nil {
// If Conditional anchor fails then we dont process the resources
if anchor.IsConditionAnchor(key) {
// If Conditional anchor fails then we don't process the resources
if commonAnchors.IsConditionAnchor(key) {
ac.AnchorError = err
log.Error(err, "condition anchor did not satisfy, wont process the resource")
return "", nil
}
return handlerPath, err
}
}
// If anchor fails then succeed validate and skip further validation of recursion
if ac.AnchorError != nil {
return "", nil
}
// Evaluate resources
for key, resourceElement := range resources {
// get handler for resources in the pattern
handler := anchor.CreateElementHandler(key, resourceElement, path)
handlerPath, err := handler.Handle(validateResourceElement, resourceMap, origPattern)
// getSortedNestedAnchorResource - keeps the anchor key to start of the list
sortedResourceKeys := getSortedNestedAnchorResource(resources)
for e := sortedResourceKeys.Front(); e != nil; e = e.Next() {
key := e.Value.(string)
handler := anchor.CreateElementHandler(key, resources[key], path)
handlerPath, err := handler.Handle(validateResourceElement, resourceMap, origPattern, ac)
if err != nil {
return handlerPath, err
}
@ -109,7 +123,7 @@ func validateMap(log logr.Logger, resourceMap, patternMap map[string]interface{}
return "", nil
}
func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, originPattern interface{}, path string) (string, error) {
func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, originPattern interface{}, path string, ac *common.AnchorKey) (string, error) {
if 0 == len(patternArray) {
return path, fmt.Errorf("Pattern Array empty")
@ -119,7 +133,7 @@ func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, o
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
path, err := validateArrayOfMaps(log, resourceArray, typedPatternElement, originPattern, path)
path, err := validateArrayOfMaps(log, resourceArray, typedPatternElement, originPattern, path, ac)
if err != nil {
return path, err
}
@ -128,7 +142,7 @@ func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, o
if len(resourceArray) >= len(patternArray) {
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
path, err := validateResourceElement(log, resourceArray[i], patternElement, originPattern, currentPath)
path, err := validateResourceElement(log, resourceArray[i], patternElement, originPattern, currentPath, ac)
if err != nil {
return path, err
}
@ -256,12 +270,12 @@ func getValueFromPattern(log logr.Logger, patternMap map[string]interface{}, key
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
// and then validates each map due to the pattern
func validateArrayOfMaps(log logr.Logger, resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
func validateArrayOfMaps(log logr.Logger, resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *common.AnchorKey) (string, error) {
for i, resourceElement := range resourceMapArray {
// check the types of resource element
// expect it to be map, but can be anything ?:(
currentPath := path + strconv.Itoa(i) + "/"
returnpath, err := validateResourceElement(log, resourceElement, patternMap, originPattern, currentPath)
returnpath, err := validateResourceElement(log, resourceElement, patternMap, originPattern, currentPath, ac)
if err != nil {
return returnpath, err
}

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"testing"
"github.com/nirmata/kyverno/pkg/engine/common"
"gotest.tools/assert"
"sigs.k8s.io/controller-runtime/pkg/log"
)
@ -101,7 +102,7 @@ func TestValidateMap(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateMap(log.Log, resource, pattern, pattern, "/")
path, err := validateMap(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.NilError(t, err)
}
@ -197,7 +198,7 @@ func TestValidateMap_AsteriskForInt(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateMap(log.Log, resource, pattern, pattern, "/")
path, err := validateMap(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
t.Log(path)
assert.NilError(t, err)
}
@ -290,7 +291,7 @@ func TestValidateMap_AsteriskForMap(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateMap(log.Log, resource, pattern, pattern, "/")
path, err := validateMap(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.NilError(t, err)
}
@ -378,7 +379,7 @@ func TestValidateMap_AsteriskForArray(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateMap(log.Log, resource, pattern, pattern, "/")
path, err := validateMap(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.NilError(t, err)
}
@ -469,7 +470,7 @@ func TestValidateMap_AsteriskFieldIsMissing(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateMap(log.Log, resource, pattern, pattern, "/")
path, err := validateMap(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "/spec/template/spec/containers/0/")
assert.Assert(t, err != nil)
}
@ -561,7 +562,7 @@ func TestValidateMap_livenessProbeIsNull(t *testing.T) {
err := json.Unmarshal(rawMap, &resource)
assert.NilError(t, err)
path, err := validateMap(log.Log, resource, pattern, pattern, "/")
path, err := validateMap(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.NilError(t, err)
}
@ -651,7 +652,7 @@ func TestValidateMap_livenessProbeIsMissing(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateMap(log.Log, resource, pattern, pattern, "/")
path, err := validateMap(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.NilError(t, err)
}
@ -697,7 +698,7 @@ func TestValidateMapElement_TwoElementsInArrayOnePass(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
// assert.Equal(t, path, "/1/object/0/key2/")
// assert.NilError(t, err)
@ -732,7 +733,7 @@ func TestValidateMapElement_OneElementInArrayPass(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.NilError(t, err)
}
@ -786,7 +787,7 @@ func TestValidateMap_CorrectRelativePathInConfig(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.NilError(t, err)
}
@ -840,7 +841,7 @@ func TestValidateMap_RelativePathDoesNotExists(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "/spec/containers/0/resources/requests/memory/")
assert.Assert(t, err != nil)
}
@ -894,7 +895,7 @@ func TestValidateMap_OnlyAnchorsInPath(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "/spec/containers/0/resources/requests/memory/")
assert.Assert(t, err != nil)
}
@ -948,7 +949,7 @@ func TestValidateMap_MalformedReferenceOnlyDolarMark(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "/spec/containers/0/resources/requests/memory/")
assert.Assert(t, err != nil)
}
@ -1002,7 +1003,7 @@ func TestValidateMap_RelativePathWithParentheses(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.NilError(t, err)
}
@ -1056,7 +1057,7 @@ func TestValidateMap_MalformedPath(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "/spec/containers/0/resources/requests/memory/")
assert.Assert(t, err != nil)
}
@ -1110,7 +1111,7 @@ func TestValidateMap_AbosolutePathExists(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.Assert(t, err == nil)
}
@ -1151,7 +1152,7 @@ func TestValidateMap_AbsolutePathToMetadata(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "")
assert.Assert(t, err == nil)
}
@ -1193,7 +1194,7 @@ func TestValidateMap_AbsolutePathToMetadata_fail(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "/spec/containers/0/image/")
assert.Assert(t, err != nil)
}
@ -1247,7 +1248,7 @@ func TestValidateMap_AbosolutePathDoesNotExists(t *testing.T) {
assert.Assert(t, json.Unmarshal(rawPattern, &pattern))
assert.Assert(t, json.Unmarshal(rawMap, &resource))
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "/spec/containers/0/resources/requests/memory/")
assert.Assert(t, err != nil)
}
@ -1351,7 +1352,7 @@ func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) {
err = json.Unmarshal(rawMap, &resource)
assert.NilError(t, err)
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/")
path, err := validateResourceElement(log.Log, resource, pattern, pattern, "/", common.NewAnchorMap())
assert.Equal(t, path, "/0/object/0/key2/")
assert.Assert(t, err != nil)
}

View file

@ -5,11 +5,11 @@ import (
"regexp"
"strconv"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
)
//ValidatePattern validates the pattern
func ValidatePattern(patternElement interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
func ValidatePattern(patternElement interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) {
switch typedPatternElement := patternElement.(type) {
case map[string]interface{}:
return validateMap(typedPatternElement, path, supportedAnchors)
@ -22,7 +22,7 @@ func ValidatePattern(patternElement interface{}, path string, supportedAnchors [
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
}
}
func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) {
// check if anchors are defined
for key, value := range patternMap {
// if key is anchor
@ -45,7 +45,7 @@ func validateMap(patternMap map[string]interface{}, path string, supportedAnchor
// addition check for existence anchor
// value must be of type list
if anchor.IsExistenceAnchor(key) {
if commonAnchors.IsExistenceAnchor(key) {
typedValue, ok := value.([]interface{})
if !ok {
return path + "/" + key, fmt.Errorf("Existence anchor should have value of type list")
@ -64,7 +64,7 @@ func validateMap(patternMap map[string]interface{}, path string, supportedAnchor
return "", nil
}
func validateArray(patternArray []interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
func validateArray(patternArray []interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) {
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
// lets validate the values now :)
@ -75,7 +75,7 @@ func validateArray(patternArray []interface{}, path string, supportedAnchors []a
return "", nil
}
func checkAnchors(key string, supportedAnchors []anchor.IsAnchor) bool {
func checkAnchors(key string, supportedAnchors []commonAnchors.IsAnchor) bool {
for _, f := range supportedAnchors {
if f(key) {
return true

View file

@ -7,7 +7,7 @@ import (
"github.com/go-logr/logr"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
"github.com/nirmata/kyverno/pkg/engine/variables"
"github.com/nirmata/kyverno/pkg/policy/common"
)
@ -60,7 +60,7 @@ func (g *Generate) Validate() (string, error) {
if rule.Data != nil {
//TODO: is this required ?? as anchors can only be on pattern and not resource
// we can add this check by not sure if its needed here
if path, err := common.ValidatePattern(rule.Data, "/", []anchor.IsAnchor{}); err != nil {
if path, err := common.ValidatePattern(rule.Data, "/", []commonAnchors.IsAnchor{}); err != nil {
return fmt.Sprintf("data.%s", path), fmt.Errorf("anchors not supported on generate resources: %v", err)
}
}

View file

@ -5,7 +5,7 @@ import (
"fmt"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
"github.com/nirmata/kyverno/pkg/policy/common"
)
@ -36,7 +36,7 @@ func (m *Mutate) Validate() (string, error) {
}
// Overlay
if rule.Overlay != nil {
path, err := common.ValidatePattern(rule.Overlay, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsAddingAnchor})
path, err := common.ValidatePattern(rule.Overlay, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsAddingAnchor})
if err != nil {
return path, err
}

View file

@ -96,6 +96,11 @@ func Validate(policyRaw []byte, client *dclient.Client, mock bool, openAPIContro
" the rule does not match an kind")
}
}
// Validate string values in labels
if !isLabelAndAnnotationsString(rule) {
return fmt.Errorf("labels and annotations supports only string values, \"use double quotes around the non string values\"")
}
}
if !mock {
@ -250,6 +255,62 @@ func doesMatchAndExcludeConflict(rule kyverno.Rule) bool {
return true
}
// isLabelAndAnnotationsString :- Validate if labels and annotations contains only string values
func isLabelAndAnnotationsString(rule kyverno.Rule) bool {
// checkMetadata - Verify if the labels and annotations contains string value inside metadata
checkMetadata := func(patternMap map[string]interface{}) bool {
for k := range patternMap {
if k == "metadata" {
metaKey, ok := patternMap[k].(map[string]interface{})
if ok {
// range over metadata
for mk := range metaKey {
if mk == "labels" {
labelKey, ok := metaKey[mk].(map[string]interface{})
if ok {
// range over labels
for _, val := range labelKey {
if reflect.TypeOf(val).String() != "string" {
return false
}
}
}
} else if mk == "annotations" {
annotationKey, ok := metaKey[mk].(map[string]interface{})
if ok {
// range over annotations
for _, val := range annotationKey {
if reflect.TypeOf(val).String() != "string" {
return false
}
}
}
}
}
}
}
}
return true
}
patternMap, ok := rule.Validation.Pattern.(map[string]interface{})
if ok {
return checkMetadata(patternMap)
} else if len(rule.Validation.AnyPattern) > 0 {
anyPatterns := rule.Validation.AnyPattern
for _, pattern := range anyPatterns {
patternMap, ok := pattern.(map[string]interface{})
if ok {
ret := checkMetadata(patternMap)
if ret == false {
return ret
}
}
}
}
return true
}
func ruleOnlyDealsWithResourceMetaData(rule kyverno.Rule) bool {
overlayMap, _ := rule.Mutation.Overlay.(map[string]interface{})
for k := range overlayMap {

View file

@ -4,7 +4,7 @@ import (
"fmt"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
commonAnchors "github.com/nirmata/kyverno/pkg/engine/anchor/common"
"github.com/nirmata/kyverno/pkg/policy/common"
)
@ -31,14 +31,14 @@ func (v *Validate) Validate() (string, error) {
}
if rule.Pattern != nil {
if path, err := common.ValidatePattern(rule.Pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistenceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
if path, err := common.ValidatePattern(rule.Pattern, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor}); err != nil {
return fmt.Sprintf("pattern.%s", path), err
}
}
if len(rule.AnyPattern) != 0 {
for i, pattern := range rule.AnyPattern {
if path, err := common.ValidatePattern(pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistenceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
if path, err := common.ValidatePattern(pattern, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor}); err != nil {
return fmt.Sprintf("anyPattern[%d].%s", i, path), err
}
}