From 48d9ebba2ce588f4f03c6b596ce2da79cc7ebd76 Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Mon, 30 Jan 2023 17:47:19 +0530 Subject: [PATCH] Replaces manually written logic with regex for matching anchor elements (#6133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * uses regular expressions Signed-off-by: Vishal Choudhary * adds regex capture Signed-off-by: Vishal Choudhary * creates anchor instance Signed-off-by: Vishal Choudhary * remove IsAnchor Signed-off-by: Charles-Edouard Brétéché * more Signed-off-by: Charles-Edouard Brétéché * added interface Signed-off-by: Vishal Choudhary * remove static funcs Signed-off-by: Charles-Edouard Brétéché * adapt Signed-off-by: Charles-Edouard Brétéché * value receiver Signed-off-by: Charles-Edouard Brétéché * simplify Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * error Signed-off-by: Charles-Edouard Brétéché * renames Signed-off-by: Charles-Edouard Brétéché * private Signed-off-by: Charles-Edouard Brétéché * nit Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * ficx Signed-off-by: Charles-Edouard Brétéché * refactor Signed-off-by: Charles-Edouard Brétéché * test Signed-off-by: Charles-Edouard Brétéché * tests Signed-off-by: Charles-Edouard Brétéché * test Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * error Signed-off-by: Charles-Edouard Brétéché * unit tests Signed-off-by: Charles-Edouard Brétéché * refactor Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * unit tests Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché --------- Signed-off-by: Vishal Choudhary Signed-off-by: Charles-Edouard Brétéché Co-authored-by: Charles-Edouard Brétéché Co-authored-by: Charles-Edouard Brétéché Co-authored-by: shuting --- pkg/engine/anchor/anchor.go | 344 +++------ pkg/engine/anchor/anchorKey.go | 167 ----- pkg/engine/anchor/anchor_test.go | 653 ++++++++++++++++++ pkg/engine/anchor/anchormap.go | 44 ++ pkg/engine/anchor/anchormap_test.go | 66 ++ pkg/engine/anchor/common.go | 127 ---- pkg/engine/anchor/common_test.go | 68 -- pkg/engine/anchor/error.go | 89 +++ pkg/engine/anchor/error_test.go | 276 ++++++++ pkg/engine/anchor/handlers.go | 275 ++++++++ pkg/engine/anchor/utils.go | 60 ++ pkg/engine/anchor/utils_test.go | 126 ++++ .../mutate/patch/strategicPreprocessing.go | 101 ++- .../patch/strategicPreprocessing_test.go | 10 +- pkg/engine/utils/utils.go | 6 +- pkg/engine/validate/utils.go | 6 +- pkg/engine/validate/validate.go | 14 +- pkg/engine/wildcards/wildcards.go | 27 +- pkg/policy/common/validate_pattern.go | 44 +- pkg/policy/generate/validate.go | 3 +- pkg/policy/validate/validate.go | 18 +- 21 files changed, 1794 insertions(+), 730 deletions(-) delete mode 100644 pkg/engine/anchor/anchorKey.go create mode 100644 pkg/engine/anchor/anchor_test.go create mode 100644 pkg/engine/anchor/anchormap.go create mode 100644 pkg/engine/anchor/anchormap_test.go delete mode 100644 pkg/engine/anchor/common.go delete mode 100644 pkg/engine/anchor/common_test.go create mode 100644 pkg/engine/anchor/error.go create mode 100644 pkg/engine/anchor/error_test.go create mode 100644 pkg/engine/anchor/handlers.go create mode 100644 pkg/engine/anchor/utils.go create mode 100644 pkg/engine/anchor/utils_test.go diff --git a/pkg/engine/anchor/anchor.go b/pkg/engine/anchor/anchor.go index 95d1f9d15a..5dc175e47e 100644 --- a/pkg/engine/anchor/anchor.go +++ b/pkg/engine/anchor/anchor.go @@ -1,277 +1,123 @@ package anchor import ( - "fmt" - "strconv" - - "github.com/go-logr/logr" - "github.com/kyverno/kyverno/pkg/logging" + "regexp" + "strings" ) -// ValidationHandler for element processes -type ValidationHandler interface { - Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) +type AnchorType string + +const ( + Condition AnchorType = "" + Global AnchorType = "<" + Negation AnchorType = "X" + AddIfNotPresent AnchorType = "+" + Equality AnchorType = "=" + Existence AnchorType = "^" +) + +var regex = regexp.MustCompile(`^(?P[+<=X^])?\((?P.+)\)$`) + +// Anchor interface +type Anchor interface { + // Type returns the anchor type + Type() AnchorType + // Key returns the anchor key + Key() string + // String returns the anchor string + String() string } -type resourceElementHandler = func(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string, ac *AnchorKey) (string, error) +type anchor struct { + modifier AnchorType + key string +} -// CreateElementHandler factory to process elements -func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler { - switch { - case IsConditionAnchor(element): - return NewConditionAnchorHandler(element, pattern, path) - case IsGlobalAnchor(element): - return NewGlobalAnchorHandler(element, pattern, path) - case IsExistenceAnchor(element): - return NewExistenceHandler(element, pattern, path) - case IsEqualityAnchor(element): - return NewEqualityHandler(element, pattern, path) - case IsNegationAnchor(element): - return NewNegationHandler(element, pattern, path) - default: - return NewDefaultHandler(element, pattern, path) +// Parse parses a string, returns nil if not an anchor +func Parse(str string) Anchor { + str = strings.TrimSpace(str) + values := regex.FindStringSubmatch(str) + if len(values) == 0 { + return nil + } + return New(AnchorType(values[1]), values[2]) +} + +// New creates an anchor +func New(modifier AnchorType, key string) Anchor { + if key == "" { + return nil + } + return anchor{ + modifier: modifier, + key: key, } } -// NewNegationHandler returns instance of negation handler -func NewNegationHandler(anchor string, pattern interface{}, path string) ValidationHandler { - return NegationHandler{ - anchor: anchor, - pattern: pattern, - path: path, +// String returns the anchor string. +// Will return an empty string if key is empty. +func String(modifier AnchorType, key string) string { + if key == "" { + return "" } + return string(modifier) + "(" + key + ")" } -// NegationHandler provides handler for check if the tag in anchor is not defined -type NegationHandler struct { - anchor string - pattern interface{} - path string +func (a anchor) Type() AnchorType { + return a.modifier } -// Handle process negation handler -func (nh NegationHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) { - anchorKey, _ := RemoveAnchor(nh.anchor) - currentPath := nh.path + anchorKey + "/" - // if anchor is present in the resource then fail - if _, ok := resourceMap[anchorKey]; ok { - // no need to process elements in value as key cannot be present in resource - ac.AnchorError = NewNegationAnchorError(fmt.Sprintf("%s is not allowed", currentPath)) - return currentPath, ac.AnchorError.Error() - } - // key is not defined in the resource - return "", nil +func (a anchor) Key() string { + return a.key } -// NewEqualityHandler returens instance of equality handler -func NewEqualityHandler(anchor string, pattern interface{}, path string) ValidationHandler { - return EqualityHandler{ - anchor: anchor, - pattern: pattern, - path: path, - } +func (a anchor) String() string { + return String(a.modifier, a.key) } -// EqualityHandler provides handler for non anchor element -type EqualityHandler struct { - anchor string - pattern interface{} - path string -} - -// Handle processed condition anchor -func (eh EqualityHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) { - anchorKey, _ := 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(logging.GlobalLogger(), value, eh.pattern, originPattern, currentPath, ac) - if err != nil { - return returnPath, err - } - return "", nil - } - return "", nil -} - -// NewDefaultHandler returns handler for non anchor elements -func NewDefaultHandler(element string, pattern interface{}, path string) ValidationHandler { - return DefaultHandler{ - element: element, - pattern: pattern, - path: path, - } -} - -// DefaultHandler provides handler for non anchor element -type DefaultHandler struct { - element string - pattern interface{} - path string -} - -// Handle process non anchor element -func (dh DefaultHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *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("%s/%s not found", dh.path, dh.element) - } else { - path, err := handler(logging.GlobalLogger(), resourceMap[dh.element], dh.pattern, originPattern, currentPath, ac) - if err != nil { - return path, err - } - } - return "", nil -} - -// NewConditionAnchorHandler returns an instance of condition acnhor handler -func NewConditionAnchorHandler(anchor string, pattern interface{}, path string) ValidationHandler { - return ConditionAnchorHandler{ - anchor: anchor, - pattern: pattern, - path: path, - } -} - -// ConditionAnchorHandler provides handler for condition anchor -type ConditionAnchorHandler struct { - anchor string - pattern interface{} - path string -} - -// Handle processed condition anchor -func (ch ConditionAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (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 := handler(logging.GlobalLogger(), value, ch.pattern, originPattern, currentPath, ac) - if err != nil { - ac.AnchorError = NewConditionalAnchorError(err.Error()) - return returnPath, ac.AnchorError.Error() - } - return "", nil - } else { - msg := "conditional anchor key doesn't exist in the resource" - return currentPath, NewConditionalAnchorError(msg).Error() - } -} - -// NewGlobalAnchorHandler returns an instance of condition acnhor handler -func NewGlobalAnchorHandler(anchor string, pattern interface{}, path string) ValidationHandler { - return GlobalAnchorHandler{ - anchor: anchor, - pattern: pattern, - path: path, - } -} - -// GlobalAnchorHandler provides handler for global condition anchor -type GlobalAnchorHandler struct { - anchor string - pattern interface{} - path string -} - -// Handle processed global condition anchor -func (gh GlobalAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) { - anchorKey, _ := RemoveAnchor(gh.anchor) - currentPath := gh.path + anchorKey + "/" - // check if anchor is present in resource - if value, ok := resourceMap[anchorKey]; ok { - // validate the values of the pattern - returnPath, err := handler(logging.GlobalLogger(), value, gh.pattern, originPattern, currentPath, ac) - if err != nil { - ac.AnchorError = NewGlobalAnchorError(err.Error()) - return returnPath, ac.AnchorError.Error() - } - return "", nil - } - return "", nil -} - -// NewExistenceHandler returns existence handler -func NewExistenceHandler(anchor string, pattern interface{}, path string) ValidationHandler { - return ExistenceHandler{ - anchor: anchor, - pattern: pattern, - path: path, - } -} - -// ExistenceHandler provides handlers to process exitence anchor handler -type ExistenceHandler struct { - anchor string - pattern interface{} - path string -} - -// Handle processes the existence anchor handler -func (eh ExistenceHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) { - // skip is used by existence anchor to not process further if condition is not satisfied - anchorKey, _ := RemoveAnchor(eh.anchor) - currentPath := eh.path + anchorKey + "/" - // check if anchor is present in resource - if value, ok := resourceMap[anchorKey]; ok { - // Existence 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 list to compare against resource", eh.pattern) +// IsOneOf returns checks if anchor is one of the given types +func IsOneOf(a Anchor, types ...AnchorType) bool { + if a != nil { + for _, t := range types { + if t == a.Type() { + return true } - // loop all item in the pattern array - errorPath := "" - var err error - for _, patternMap := range typedPattern { - 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) - } - errorPath, err = validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath, ac) - if err != nil { - return errorPath, err - } - } - return errorPath, err - default: - return currentPath, fmt.Errorf("invalid resource type %T: Existence ^ () anchor can be used only on list/array type resource", value) } } - return "", nil + return false } -func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *AnchorKey) (string, error) { - // the idea is all the element in the pattern array should be present atleast once in the resource list - // if non satisfy then throw an error - for i, resourceElement := range resourceList { - currentPath := path + strconv.Itoa(i) + "/" - _, err := handler(logging.GlobalLogger(), resourceElement, patternMap, originPattern, currentPath, ac) - if err == nil { - // condition is satisfied, dont check further - return "", nil - } - } - // none of the existence checks worked, so thats a failure sceanario - return path, fmt.Errorf("existence anchor validation failed at path %s", path) +// ContainsCondition returns true if anchor is either condition anchor or global condition anchor +func ContainsCondition(a Anchor) bool { + return IsOneOf(a, Condition, Global) } -// GetAnchorsResourcesFromMap returns map of anchors -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) || IsExistenceAnchor(key) || IsEqualityAnchor(key) || IsNegationAnchor(key) { - anchors[key] = value - continue - } - resources[key] = value - } - - return anchors, resources +// IsCondition checks for condition anchor +func IsCondition(a Anchor) bool { + return IsOneOf(a, Condition) +} + +// IsGlobal checks for global condition anchor +func IsGlobal(a Anchor) bool { + return IsOneOf(a, Global) +} + +// IsNegation checks for negation anchor +func IsNegation(a Anchor) bool { + return IsOneOf(a, Negation) +} + +// IsAddIfNotPresent checks for addition anchor +func IsAddIfNotPresent(a Anchor) bool { + return IsOneOf(a, AddIfNotPresent) +} + +// IsEquality checks for equality anchor +func IsEquality(a Anchor) bool { + return IsOneOf(a, Equality) +} + +// IsExistence checks for existence anchor +func IsExistence(a Anchor) bool { + return IsOneOf(a, Existence) } diff --git a/pkg/engine/anchor/anchorKey.go b/pkg/engine/anchor/anchorKey.go deleted file mode 100644 index 44c50c4ea1..0000000000 --- a/pkg/engine/anchor/anchorKey.go +++ /dev/null @@ -1,167 +0,0 @@ -package anchor - -import ( - "errors" - "fmt" - "strings" -) - -// IsNegationAnchorError checks if error message has negation anchor error string -func IsNegationAnchorError(msg string) bool { - return strings.Contains(msg, NegationAnchorErrMsg) -} - -// IsConditionalAnchorError checks if error message has conditional anchor error string -func IsConditionalAnchorError(msg string) bool { - return strings.Contains(msg, ConditionalAnchorErrMsg) -} - -// IsGlobalAnchorError checks if error message has global anchor error string -func IsGlobalAnchorError(msg string) bool { - return strings.Contains(msg, GlobalAnchorErrMsg) -} - -// NewNegationAnchorError returns a new instance of NegationAnchorError -func NewNegationAnchorError(msg string) ValidateAnchorError { - return ValidateAnchorError{ - Err: NegationAnchorErr, - Message: fmt.Sprintf("%s: %s", NegationAnchorErrMsg, msg), - } -} - -// IsNegationAnchorError checks if the error is a negation anchor error -func (e ValidateAnchorError) IsNegationAnchorError() bool { - return e.Err == NegationAnchorErr -} - -// NewConditionalAnchorError returns a new instance of ConditionalAnchorError -func NewConditionalAnchorError(msg string) ValidateAnchorError { - return ValidateAnchorError{ - Err: ConditionalAnchorErr, - Message: fmt.Sprintf("%s: %s", ConditionalAnchorErrMsg, msg), - } -} - -// IsConditionAnchorError checks if the error is a conditional anchor error -func (e ValidateAnchorError) IsConditionAnchorError() bool { - return e.Err == ConditionalAnchorErr -} - -// NewGlobalAnchorError returns a new instance of GlobalAnchorError -func NewGlobalAnchorError(msg string) ValidateAnchorError { - return ValidateAnchorError{ - Err: GlobalAnchorErr, - Message: fmt.Sprintf("%s: %s", GlobalAnchorErrMsg, msg), - } -} - -// IsGlobalAnchorError checks if the error is a global anchor error -func (e ValidateAnchorError) IsGlobalAnchorError() bool { - return e.Err == GlobalAnchorErr -} - -// IsNil checks if the error isn't populated -func (e ValidateAnchorError) IsNil() bool { - return e == ValidateAnchorError{} -} - -// Error returns an error instance of the anchor error -func (e ValidateAnchorError) Error() error { - return errors.New(e.Message) -} - -// AnchorError is the const specification of anchor errors -type AnchorError int - -const ( - // ConditionalAnchorErr refers to condition violation - ConditionalAnchorErr AnchorError = iota - - // GlobalAnchorErr refers to global condition violation - GlobalAnchorErr - - // NegationAnchorErr refers to negation violation - NegationAnchorErr -) - -// ValidateAnchorError represents the error type of validation anchors -type ValidateAnchorError struct { - Err AnchorError - Message string -} - -// NegationAnchorErrMsg - the error message for negation anchor error -var NegationAnchorErrMsg = "negation anchor matched in resource" - -// ConditionalAnchorErrMsg - the error message for conditional anchor error -var ConditionalAnchorErrMsg = "conditional anchor mismatch" - -// GlobalAnchorErrMsg - the error message for global anchor error -var GlobalAnchorErrMsg = "global anchor mismatch" - -// AnchorKey - contains map of anchors -type AnchorKey struct { - // anchorMap - for each anchor key in the patterns it will maintain 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 ValidateAnchorError -} - -// 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 { - return true - } - } - return false -} - -// CheckAnchorInResource checks 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 IsConditionAnchor(key) || IsExistenceAnchor(key) || IsNegationAnchor(key) { - val, ok := ac.anchorMap[key] - if !ok { - ac.anchorMap[key] = false - } else if ok && val { - 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, _ := 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 - } -} diff --git a/pkg/engine/anchor/anchor_test.go b/pkg/engine/anchor/anchor_test.go new file mode 100644 index 0000000000..4f540e512c --- /dev/null +++ b/pkg/engine/anchor/anchor_test.go @@ -0,0 +1,653 @@ +package anchor + +import ( + "reflect" + "testing" +) + +func TestNew(t *testing.T) { + type args struct { + modifier AnchorType + key string + } + tests := []struct { + name string + args args + want Anchor + }{{ + args: args{Condition, ""}, + want: nil, + }, { + args: args{Global, ""}, + want: nil, + }, { + args: args{Negation, ""}, + want: nil, + }, { + args: args{AddIfNotPresent, ""}, + want: nil, + }, { + args: args{Equality, ""}, + want: nil, + }, { + args: args{Existence, ""}, + want: nil, + }, { + args: args{Condition, "test"}, + want: anchor{Condition, "test"}, + }, { + args: args{Global, "test"}, + want: anchor{Global, "test"}, + }, { + args: args{Negation, "test"}, + want: anchor{Negation, "test"}, + }, { + args: args{AddIfNotPresent, "test"}, + want: anchor{AddIfNotPresent, "test"}, + }, { + args: args{Equality, "test"}, + want: anchor{Equality, "test"}, + }, { + args: args{Existence, "test"}, + want: anchor{Existence, "test"}, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.modifier, tt.args.key); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestString(t *testing.T) { + type args struct { + modifier AnchorType + key string + } + tests := []struct { + name string + args args + want string + }{{ + args: args{Condition, ""}, + want: "", + }, { + args: args{Global, ""}, + want: "", + }, { + args: args{Negation, ""}, + want: "", + }, { + args: args{AddIfNotPresent, ""}, + want: "", + }, { + args: args{Equality, ""}, + want: "", + }, { + args: args{Existence, ""}, + want: "", + }, { + args: args{Condition, "test"}, + want: "(test)", + }, { + args: args{Global, "test"}, + want: "<(test)", + }, { + args: args{Negation, "test"}, + want: "X(test)", + }, { + args: args{AddIfNotPresent, "test"}, + want: "+(test)", + }, { + args: args{Equality, "test"}, + want: "=(test)", + }, { + args: args{Existence, "test"}, + want: "^(test)", + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := String(tt.args.modifier, tt.args.key); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsOneOf(t *testing.T) { + type args struct { + a Anchor + types []AnchorType + } + tests := []struct { + name string + args args + want bool + }{{ + args: args{}, + want: false, + }, { + args: args{nil, []AnchorType{Condition, Negation}}, + want: false, + }, { + args: args{New(Condition, "test"), nil}, + want: false, + }, { + args: args{New(Condition, "test"), []AnchorType{}}, + want: false, + }, { + args: args{New(Condition, "test"), []AnchorType{Condition}}, + want: true, + }, { + args: args{New(Condition, "test"), []AnchorType{Condition, Negation}}, + want: true, + }, { + args: args{New(Condition, "test"), []AnchorType{Negation, Global}}, + want: false, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsOneOf(tt.args.a, tt.args.types...); got != tt.want { + t.Errorf("IsOneOf() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParse(t *testing.T) { + type args struct { + str string + } + tests := []struct { + name string + args args + want Anchor + }{ + { + args: args{"(something)"}, + want: anchor{Condition, "something"}, + }, { + args: args{"()"}, + want: nil, + }, { + args: args{"something"}, + want: nil, + }, { + args: args{"(something"}, + want: nil, + }, { + args: args{"something)"}, + want: nil, + }, { + args: args{"so)m(et(hin)g"}, + want: nil, + }, { + args: args{""}, + want: nil, + }, { + args: args{"^(abc)"}, + want: anchor{Existence, "abc"}, + }, { + args: args{"^(abc"}, + want: nil, + }, { + args: args{"^abc"}, + want: nil, + }, { + args: args{"^()"}, + want: nil, + }, { + args: args{"(abc)"}, + want: anchor{Condition, "abc"}, + }, { + args: args{"=(abc)"}, + want: anchor{Equality, "abc"}, + }, { + args: args{"=(abc"}, + want: nil, + }, { + args: args{"=abc"}, + want: nil, + }, { + args: args{"+(abc)"}, + want: anchor{AddIfNotPresent, "abc"}, + }, { + args: args{"+(abc"}, + want: nil, + }, { + args: args{"+abc"}, + want: nil, + }, { + args: args{"X(abc)"}, + want: anchor{Negation, "abc"}, + }, { + args: args{"X(abc"}, + want: nil, + }, { + args: args{"Xabc"}, + want: nil, + }, { + args: args{"<(abc)"}, + want: anchor{Global, "abc"}, + }, { + args: args{"<(abc"}, + want: nil, + }, { + args: args{" this is anchor - // () - // single char () - re, err := regexp.Compile(`^.?\(.+\)$`) - if err != nil { - return path + "/" + key, fmt.Errorf("unable to parse the field %s: %v", key, err) - } - - matched := re.MatchString(key) + a := anchor.Parse(key) // check the type of anchor - if matched { + if a != nil { // some type of anchor // check if valid anchor - if !checkAnchors(key, supportedAnchors) { + if !checkAnchors(a, isSupported) { return path + "/" + key, fmt.Errorf("unsupported anchor %s", key) } - // addition check for existence anchor // value must be of type list - if commonAnchors.IsExistenceAnchor(key) { + if anchor.IsExistence(a) { typedValue, ok := value.([]interface{}) if !ok { return path + "/" + key, fmt.Errorf("existence anchor should have value of type list") @@ -58,29 +48,27 @@ func validateMap(patternMap map[string]interface{}, path string, supportedAnchor } } // lets validate the values now :) - if errPath, err := ValidatePattern(value, path+"/"+key, supportedAnchors); err != nil { + if errPath, err := ValidatePattern(value, path+"/"+key, isSupported); err != nil { return errPath, err } } return "", nil } -func validateArray(patternArray []interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) { +func validateArray(patternArray []interface{}, path string, isSupported func(anchor.Anchor) bool) (string, error) { for i, patternElement := range patternArray { currentPath := path + strconv.Itoa(i) + "/" // lets validate the values now :) - if errPath, err := ValidatePattern(patternElement, currentPath, supportedAnchors); err != nil { + if errPath, err := ValidatePattern(patternElement, currentPath, isSupported); err != nil { return errPath, err } } return "", nil } -func checkAnchors(key string, supportedAnchors []commonAnchors.IsAnchor) bool { - for _, f := range supportedAnchors { - if f(key) { - return true - } +func checkAnchors(a anchor.Anchor, isSupported func(anchor.Anchor) bool) bool { + if isSupported == nil { + return false } - return false + return isSupported(a) } diff --git a/pkg/policy/generate/validate.go b/pkg/policy/generate/validate.go index 0d1e2875c5..2f4a29a94d 100644 --- a/pkg/policy/generate/validate.go +++ b/pkg/policy/generate/validate.go @@ -8,7 +8,6 @@ import ( "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/clients/dclient" - commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor" "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/policy/common" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" @@ -79,7 +78,7 @@ func (g *Generate) Validate() (string, error) { if target := rule.GetData(); target != 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(target, "/", []commonAnchors.IsAnchor{}); err != nil { + if path, err := common.ValidatePattern(target, "/", nil); err != nil { return fmt.Sprintf("data.%s", path), fmt.Errorf("anchors not supported on generate resources: %v", err) } } diff --git a/pkg/policy/validate/validate.go b/pkg/policy/validate/validate.go index 5d0d88e6a2..7c9d792a03 100644 --- a/pkg/policy/validate/validate.go +++ b/pkg/policy/validate/validate.go @@ -4,7 +4,7 @@ import ( "fmt" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor" + "github.com/kyverno/kyverno/pkg/engine/anchor" "github.com/kyverno/kyverno/pkg/policy/common" ) @@ -30,7 +30,13 @@ func (v *Validate) Validate() (string, error) { } if target := v.rule.GetPattern(); target != nil { - if path, err := common.ValidatePattern(target, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor, commonAnchors.IsGlobalAnchor}); err != nil { + if path, err := common.ValidatePattern(target, "/", func(a anchor.Anchor) bool { + return anchor.IsCondition(a) || + anchor.IsExistence(a) || + anchor.IsEquality(a) || + anchor.IsNegation(a) || + anchor.IsGlobal(a) + }); err != nil { return fmt.Sprintf("pattern.%s", path), err } } @@ -41,7 +47,13 @@ func (v *Validate) Validate() (string, error) { return "anyPattern", fmt.Errorf("failed to deserialize anyPattern, expect array: %v", err) } for i, pattern := range anyPattern { - if path, err := common.ValidatePattern(pattern, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor, commonAnchors.IsGlobalAnchor}); err != nil { + if path, err := common.ValidatePattern(pattern, "/", func(a anchor.Anchor) bool { + return anchor.IsCondition(a) || + anchor.IsExistence(a) || + anchor.IsEquality(a) || + anchor.IsNegation(a) || + anchor.IsGlobal(a) + }); err != nil { return fmt.Sprintf("anyPattern[%d].%s", i, path), err } }