1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

policy validation: refactoring

This commit is contained in:
shivkumar dudhani 2019-10-21 14:22:31 -07:00
parent 68c87a09ec
commit 3fa8834b4a
13 changed files with 708 additions and 895 deletions

View file

@ -5,6 +5,7 @@ import (
"strconv"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/anchor"
)
//ValidationHandler for element processes
@ -15,19 +16,20 @@ type ValidationHandler interface {
//CreateElementHandler factory to process elements
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
switch {
case isConditionAnchor(element):
case anchor.IsConditionAnchor(element):
return NewConditionAnchorHandler(element, pattern, path)
case isExistanceAnchor(element):
case anchor.IsExistanceAnchor(element):
return NewExistanceHandler(element, pattern, path)
case isEqualityAnchor(element):
case anchor.IsEqualityAnchor(element):
return NewEqualityHandler(element, pattern, path)
case isNegationAnchor(element):
case anchor.IsNegationAnchor(element):
return NewNegationHandler(element, pattern, path)
default:
return NewDefaultHandler(element, pattern, path)
}
}
//NewNegationHandler returns instance of negation handler
func NewNegationHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return NegationHandler{
anchor: anchor,
@ -56,6 +58,7 @@ func (nh NegationHandler) Handle(resourceMap map[string]interface{}, originPatte
return "", nil
}
//NewEqualityHandler returens instance of equality handler
func NewEqualityHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return EqualityHandler{
anchor: anchor,
@ -217,7 +220,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) || isExistanceAnchor(key) || isEqualityAnchor(key) || isNegationAnchor(key) {
if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) || anchor.IsEqualityAnchor(key) || anchor.IsNegationAnchor(key) {
anchors[key] = value
continue
}

View file

@ -0,0 +1,54 @@
package anchor
// Anchor function type
type IsAnchor func(str string) bool
func IsConditionAnchor(str string) bool {
if len(str) < 2 {
return false
}
return (str[0] == '(' && str[len(str)-1] == ')')
}
func IsNegationAnchor(str string) bool {
left := "X("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
//TODO: trim spaces ?
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
func IsAddingAnchor(key string) bool {
const left = "+("
const right = ")"
if len(key) < len(left)+len(right) {
return false
}
return left == key[:len(left)] && right == key[len(key)-len(right):]
}
func IsEqualityAnchor(str string) bool {
left := "=("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
//TODO: trim spaces ?
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
func IsExistanceAnchor(str string) bool {
left := "^("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}

View file

@ -0,0 +1,58 @@
package anchor
import (
"testing"
"gotest.tools/assert"
)
func TestWrappedWithParentheses_StringIsWrappedWithParentheses(t *testing.T) {
str := "(something)"
assert.Assert(t, IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasOnlyParentheses(t *testing.T) {
str := "()"
assert.Assert(t, IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasNoParentheses(t *testing.T) {
str := "something"
assert.Assert(t, !IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasLeftParentheses(t *testing.T) {
str := "(something"
assert.Assert(t, !IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasRightParentheses(t *testing.T) {
str := "something)"
assert.Assert(t, !IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringParenthesesInside(t *testing.T) {
str := "so)m(et(hin)g"
assert.Assert(t, !IsConditionAnchor(str))
}
func TestWrappedWithParentheses_Empty(t *testing.T) {
str := ""
assert.Assert(t, !IsConditionAnchor(str))
}
func TestIsExistanceAnchor_Yes(t *testing.T) {
assert.Assert(t, IsExistanceAnchor("^(abc)"))
}
func TestIsExistanceAnchor_NoRightBracket(t *testing.T) {
assert.Assert(t, !IsExistanceAnchor("^(abc"))
}
func TestIsExistanceAnchor_OnlyHat(t *testing.T) {
assert.Assert(t, !IsExistanceAnchor("^abc"))
}
func TestIsExistanceAnchor_ConditionAnchor(t *testing.T) {
assert.Assert(t, !IsExistanceAnchor("(abc)"))
}

View file

@ -38,7 +38,7 @@ func Generate(client *client.Client, policy kyverno.ClusterPolicy, ns unstructur
response.PolicyResponse.RulesAppliedCount++
}
for _, rule := range policy.Spec.Rules {
if rule.Generation == (kyverno.Generation{}) {
if !rule.HasGenerate() {
continue
}
glog.V(4).Infof("applying policy %s generate rule %s on resource %s/%s/%s", policy.Name, rule.Name, ns.GetKind(), ns.GetNamespace(), ns.GetName())

View file

@ -37,7 +37,7 @@ func Mutate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (r
for _, rule := range policy.Spec.Rules {
//TODO: to be checked before calling the resources as well
if reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) {
if !rule.HasMutate() {
continue
}
// check if the resource satisfies the filter conditions defined in the rule

View file

@ -14,6 +14,7 @@ import (
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
)
// processOverlay processes validation patterns on the resource
@ -148,7 +149,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 isConditionAnchor(key) {
if anchor.IsConditionAnchor(key) {
continue
}
@ -156,7 +157,7 @@ func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path stri
currentPath := path + noAnchorKey + "/"
resourcePart, ok := resourceMap[noAnchorKey]
if ok && !isAddingAnchor(key) {
if ok && !anchor.IsAddingAnchor(key) {
// Key exists - go down through the overlay and resource trees
patches, err := applyOverlay(resourcePart, value, currentPath)
if err != nil {

View file

@ -4,6 +4,7 @@ import (
"reflect"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/anchor"
)
func meetConditions(resource, overlay interface{}) bool {
@ -51,7 +52,7 @@ func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}) bool {
for key, value := range overlayMap {
resourcePart, ok := resourceMap[key]
if ok && !isAddingAnchor(key) {
if ok && !anchor.IsAddingAnchor(key) {
if !meetConditions(resourcePart, value) {
return false
}

View file

@ -1,26 +1,5 @@
package policy
import (
"errors"
"fmt"
"strings"
)
// joinErrs joins the list of error into single error
// adds a new line between errors
func joinErrs(errs []error) error {
if len(errs) == 0 {
return nil
}
res := "\n"
for _, err := range errs {
res = fmt.Sprintf(res + err.Error() + "\n")
}
return errors.New(res)
}
//Contains Check if strint is contained in a list of string
func containString(list []string, element string) bool {
for _, e := range list {
@ -30,73 +9,3 @@ func containString(list []string, element string) bool {
}
return false
}
// hasExistingAnchor checks if str has existing anchor
// strip anchor if necessary
func hasExistingAnchor(str string) (bool, string) {
left := "^("
right := ")"
if len(str) < len(left)+len(right) {
return false, str
}
return (str[:len(left)] == left && str[len(str)-len(right):] == right), str[len(left) : len(str)-len(right)]
}
// hasValidAnchors checks str has the valid anchor
// mutate: (), +()
// validate: (), ^(), =(), X()
// generate: none
// invalid anchors: ~(),!()
func hasValidAnchors(anchors []anchor, str string) (bool, string) {
if wrappedWithAttributes(str) {
return mustWrapWithAnchors(anchors, str)
}
return true, str
}
// mustWrapWithAnchors validates str must wrap with
// at least one given anchor
func mustWrapWithAnchors(anchors []anchor, str string) (bool, string) {
for _, a := range anchors {
if str[:len(a.left)] == a.left && str[len(str)-len(a.right):] == a.right {
return true, str[len(a.left) : len(str)-len(a.right)]
}
}
return false, str
}
func wrappedWithAttributes(str string) bool {
if len(str) < 2 {
return false
}
if (str[0] == '(' && str[len(str)-1] == ')') ||
(str[0] == '^' || str[0] == '+' || str[0] == '=' || str[0] == 'X' || str[0] == '!' || str[0] == '~') &&
(str[1] == '(' && str[len(str)-1] == ')') {
return true
}
return false
}
func joinAnchors(anchorPatterns []anchor) string {
var res []string
for _, a := range anchorPatterns {
res = append(res, a.left+a.right)
}
return strings.Join(res, " || ")
}
func hasNegationAnchor(str string) (bool, string) {
left := "X("
right := ")"
if len(str) < len(left)+len(right) {
return false, str
}
return (str[:len(left)] == left && str[len(str)-len(right):] == right), str[len(left) : len(str)-len(right)]
}

View file

@ -4,89 +4,84 @@ import (
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type anchor struct {
left string
right string
}
var (
conditionalAnchor = anchor{left: "(", right: ")"}
existingAnchor = anchor{left: "^(", right: ")"}
equalityAnchor = anchor{left: "=(", right: ")"}
plusAnchor = anchor{left: "+(", right: ")"}
negationAnchor = anchor{left: "X(", right: ")"}
)
// Validate does some initial check to verify some conditions
// - One operation per rule
// - ResourceDescription mandatory checks
func Validate(p kyverno.ClusterPolicy) error {
var errs []error
if err := validateUniqueRuleName(p); err != nil {
errs = append(errs, fmt.Errorf("- Invalid Policy '%s':", p.Name))
errs = append(errs, err)
if path, err := validateUniqueRuleName(p); err != nil {
return fmt.Errorf("path: spec.%s: %v", path, err)
}
for _, rule := range p.Spec.Rules {
if ruleErrs := validateRule(rule); len(ruleErrs) != 0 {
errs = append(errs, fmt.Errorf("- invalid rule '%s':", rule.Name))
errs = append(errs, ruleErrs...)
for i, rule := range p.Spec.Rules {
// only one type of rule is allowed per rule
if err := validateRuleType(rule); err != nil {
return fmt.Errorf("path: spec.rules[%d]: %v", i, err)
}
}
return joinErrs(errs)
}
// ValidateUniqueRuleName checks if the rule names are unique across a policy
func validateUniqueRuleName(p kyverno.ClusterPolicy) error {
var ruleNames []string
for _, rule := range p.Spec.Rules {
if containString(ruleNames, rule.Name) {
return fmt.Errorf(`duplicate rule name: '%s'`, rule.Name)
// validate resource description
if path, err := validateResources(rule); err != nil {
return fmt.Errorf("path: spec.rules[%d].%s: %v", i, path, err)
}
// validate rule types
// only one type of rule is allowed per rule
if err := validateRuleType(rule); err != nil {
// as there are more than 1 operation in rule, not need to evaluate it further
return fmt.Errorf("path: spec.rules[%d]: %v", i, err)
}
// Operation Validation
// Mutation
if rule.HasMutate() {
if path, err := validateMutation(rule.Mutation); err != nil {
return fmt.Errorf("path: spec.rules[%d].mutate.%s.: %v", i, path, err)
}
}
// Validation
if rule.HasValidate() {
if path, err := validateValidation(rule.Validation); err != nil {
return fmt.Errorf("path: spec.rules[%d].validate.%s.: %v", i, path, err)
}
}
// Generation
if rule.HasGenerate() {
if path, err := validateGeneration(rule.Generation); err != nil {
return fmt.Errorf("path: spec.rules[%d].generate.%s.: %v", i, path, err)
}
}
ruleNames = append(ruleNames, rule.Name)
}
return nil
}
// Validate checks if rule is not empty and all substructures are valid
func validateRule(r kyverno.Rule) []error {
var errs []error
// only one type of rule is allowed per rule
if err := validateRuleType(r); err != nil {
errs = append(errs, err)
func validateResources(rule kyverno.Rule) (string, error) {
// matched resources
if path, err := validateMatchedResourceDescription(rule.MatchResources.ResourceDescription); err != nil {
return fmt.Sprintf("resources.%s", path), err
}
// validate resource description block
if err := validateMatchedResourceDescription(r.MatchResources.ResourceDescription); err != nil {
errs = append(errs, fmt.Errorf("error in match block, %v", err))
// exclude resources
if path, err := validateExcludeResourceDescription(rule.ExcludeResources.ResourceDescription); err != nil {
return fmt.Sprintf("resources.%s", path), err
}
return "", nil
}
if err := validateResourceDescription(r.ExcludeResources.ResourceDescription); err != nil {
errs = append(errs, fmt.Errorf("error in exclude block, %v", err))
// ValidateUniqueRuleName checks if the rule names are unique across a policy
func validateUniqueRuleName(p kyverno.ClusterPolicy) (string, error) {
var ruleNames []string
for i, rule := range p.Spec.Rules {
if containString(ruleNames, rule.Name) {
return fmt.Sprintf("rule[%d]", i), fmt.Errorf(`duplicate rule name: '%s'`, rule.Name)
}
ruleNames = append(ruleNames, rule.Name)
}
// validate anchors on mutate
if mErrs := validateMutation(r.Mutation); len(mErrs) != 0 {
errs = append(errs, mErrs...)
}
if vErrs := validateValidation(r.Validation); len(vErrs) != 0 {
errs = append(errs, vErrs...)
}
if err := validateGeneration(r.Generation); err != nil {
errs = append(errs, err)
}
return errs
return "", nil
}
// validateRuleType checks only one type of rule is defined per rule
@ -104,7 +99,7 @@ func validateRuleType(r kyverno.Rule) error {
}()
if operationCount == 0 {
return fmt.Errorf("no operation defined in the rule '%s'.(supported operations: mutation,validation,generation,query)", r.Name)
return fmt.Errorf("no operation defined in the rule '%s'.(supported operations: mutation,validation,generation)", r.Name)
} else if operationCount != 1 {
return fmt.Errorf("multiple operations defined in the rule '%s', only one type of operation is allowed per rule", r.Name)
}
@ -116,16 +111,31 @@ func validateRuleType(r kyverno.Rule) error {
// Returns error if
// - kinds is empty array in matched resource block, i.e. kinds: []
// - selector is invalid
func validateMatchedResourceDescription(rd kyverno.ResourceDescription) error {
func validateMatchedResourceDescription(rd kyverno.ResourceDescription) (string, error) {
if reflect.DeepEqual(rd, kyverno.ResourceDescription{}) {
return nil
return "", fmt.Errorf("match resources not specified")
}
if len(rd.Kinds) == 0 {
return errors.New("field Kind is not specified")
return "match", fmt.Errorf("kind is mandatory")
}
return validateResourceDescription(rd)
if err := validateResourceDescription(rd); err != nil {
return "match", err
}
return "", nil
}
func validateExcludeResourceDescription(rd kyverno.ResourceDescription) (string, error) {
if reflect.DeepEqual(rd, kyverno.ResourceDescription{}) {
// exclude is not mandatory
return "", nil
}
if err := validateResourceDescription(rd); err != nil {
return "exclude", err
}
return "", nil
}
// validateResourceDescription returns error if selector is invalid
@ -144,22 +154,23 @@ func validateResourceDescription(rd kyverno.ResourceDescription) error {
return nil
}
func validateMutation(m kyverno.Mutation) []error {
var errs []error
func validateMutation(m kyverno.Mutation) (string, error) {
// JSON Patches
if len(m.Patches) != 0 {
for _, patch := range m.Patches {
err := validatePatch(patch)
errs = append(errs, err)
for i, patch := range m.Patches {
if err := validatePatch(patch); err != nil {
return fmt.Sprintf("patch[%d]", i), err
}
}
}
// Overlay
if m.Overlay != nil {
_, err := validateAnchors([]anchor{conditionalAnchor, plusAnchor}, m.Overlay, "/")
path, err := validatePattern(m.Overlay, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsAddingAnchor})
if err != nil {
errs = append(errs, err)
return path, err
}
}
return errs
return "", nil
}
// Validate if all mandatory PolicyPatch fields are set
@ -167,7 +178,6 @@ func validatePatch(pp kyverno.Patch) error {
if pp.Path == "" {
return errors.New("JSONPatch field 'path' is mandatory")
}
if pp.Operation == "add" || pp.Operation == "replace" {
if pp.Value == nil {
return fmt.Errorf("JSONPatch field 'value' is mandatory for operation '%s'", pp.Operation)
@ -181,151 +191,154 @@ func validatePatch(pp kyverno.Patch) error {
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
}
func validateValidation(v kyverno.Validation) []error {
var errs []error
func validateValidation(v kyverno.Validation) (string, error) {
if err := validateOverlayPattern(v); err != nil {
errs = append(errs, err)
// no need to proceed ahead
return "", err
}
if v.Pattern != nil {
if _, err := validateAnchors([]anchor{conditionalAnchor, existingAnchor, equalityAnchor, negationAnchor}, v.Pattern, "/"); err != nil {
errs = append(errs, err)
if path, err := validatePattern(v.Pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistanceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
return fmt.Sprintf("pattern.%s", path), err
}
}
if len(v.AnyPattern) != 0 {
for _, p := range v.AnyPattern {
if _, err := validateAnchors([]anchor{conditionalAnchor, existingAnchor, equalityAnchor, negationAnchor}, p, "/"); err != nil {
errs = append(errs, err)
for i, pattern := range v.AnyPattern {
if path, err := validatePattern(pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistanceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
return fmt.Sprintf("anyPattern[%d].%s", i, path), err
}
}
}
return errs
return "", nil
}
// validateOverlayPattern checks one of pattern/anyPattern must exist
func validateOverlayPattern(v kyverno.Validation) error {
if reflect.DeepEqual(v, kyverno.Validation{}) {
return nil
}
if v.Pattern == nil && len(v.AnyPattern) == 0 {
return fmt.Errorf("neither pattern nor anyPattern found")
return fmt.Errorf("a pattern or anyPattern must be specified")
}
if v.Pattern != nil && len(v.AnyPattern) != 0 {
return fmt.Errorf("either pattern or anyPattern is allowed")
return fmt.Errorf("only one operation allowed per validation rule(pattern or anyPattern)")
}
return nil
}
// Validate returns error if generator is configured incompletely
func validateGeneration(gen kyverno.Generation) error {
if reflect.DeepEqual(gen, kyverno.Generation{}) {
return nil
}
func validateGeneration(gen kyverno.Generation) (string, error) {
if gen.Data == nil && gen.Clone == (kyverno.CloneFrom{}) {
return fmt.Errorf("neither data nor clone (source) of %s is specified", gen.Kind)
return "", fmt.Errorf("clone or data are required")
}
if gen.Data != nil && gen.Clone != (kyverno.CloneFrom{}) {
return fmt.Errorf("both data nor clone (source) of %s are specified", gen.Kind)
return "", fmt.Errorf("only one operation allowed per generate rule(data or clone)")
}
if gen.Data != nil {
if _, err := validateAnchors(nil, gen.Data, "/"); err != nil {
return fmt.Errorf("anchors are not allowed on generate pattern data: %v", err)
}
// check kind is non empty
// check name is non empty
if gen.Name == "" {
return "name", fmt.Errorf("name cannot be empty")
}
if gen.Kind == "" {
return "kind", fmt.Errorf("kind cannot be empty")
}
if !reflect.DeepEqual(gen.Clone, kyverno.CloneFrom{}) {
if _, err := validateAnchors(nil, gen.Clone, ""); err != nil {
return fmt.Errorf("invalid character found on pattern clone: %v", err)
if path, err := validateClone(gen.Clone); err != nil {
return fmt.Sprintf("clone.%s", path), err
}
}
return nil
if gen.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 := validatePattern(gen.Data, "/", []anchor.IsAnchor{}); err != nil {
return fmt.Sprintf("data.%s", path), fmt.Errorf("anchors not supported on generate resoruces: %v", err)
}
}
return "", nil
}
// validateAnchors validates:
// 1. existing acnchor must define on array
// 2. anchors in mutation must be one of: (), +()
// 3. anchors in validate must be one of: (), ^(), =(), X()
// 4. no anchor is allowed in generate
func validateAnchors(anchorPatterns []anchor, pattern interface{}, path string) (string, error) {
switch typedPattern := pattern.(type) {
func validateClone(c kyverno.CloneFrom) (string, error) {
if c.Name == "" {
return "name", fmt.Errorf("name cannot be empty")
}
if c.Namespace == "" {
return "namespace", fmt.Errorf("namespace cannot be empty")
}
return "", nil
}
func validatePattern(patternElement interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
switch typedPatternElement := patternElement.(type) {
case map[string]interface{}:
return validateAnchorsOnMap(anchorPatterns, typedPattern, path)
return validateMap(typedPatternElement, path, supportedAnchors)
case []interface{}:
return validateAnchorsOnArray(anchorPatterns, typedPattern, path)
return validateArray(typedPatternElement, path, supportedAnchors)
case string, float64, int, int64, bool, nil:
// check on type string
if checkedPattern := reflect.ValueOf(pattern); checkedPattern.Kind() == reflect.String {
if hasAnchor, str := hasExistingAnchor(checkedPattern.String()); hasAnchor {
return path, fmt.Errorf("existing anchor at %s must be of type array, found: %v", path+str, checkedPattern.Kind())
}
}
// return nil on all other cases
return "", nil
case interface{}:
// special case for generate clone, as it is a struct
if clone, ok := pattern.(kyverno.CloneFrom); ok {
return "", validateAnchorsOnCloneFrom(nil, clone)
}
//TODO? check operator
return "", nil
default:
glog.V(4).Infof("Pattern contains unknown type %T. Path: %s", pattern, path)
return path, fmt.Errorf("pattern contains unknown type, path: %s", path)
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
}
return "", nil
}
func validateAnchorsOnCloneFrom(anchorPatterns []anchor, pattern kyverno.CloneFrom) error {
// namespace and name are required fields
// if wrapped with invalid character, this field is empty during unmarshaling
if pattern.Namespace == "" {
return errors.New("namespace is requried")
}
if pattern.Name == "" {
return errors.New("name is requried")
}
return nil
}
func validateAnchorsOnMap(anchorPatterns []anchor, pattern map[string]interface{}, path string) (string, error) {
for key, patternElement := range pattern {
if valid, str := hasValidAnchors(anchorPatterns, key); !valid {
return path, fmt.Errorf("invalid anchor found at %s, expect: %s", path+str, joinAnchors(anchorPatterns))
func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
// check if anchors are defined
for key, value := range patternMap {
// if key is anchor
// check regex () -> this is anchor
// ()
// single char ()
matched, err := regexp.MatchString(`^.?\(.+\)$`, key)
if err != nil {
return path + "/" + key, fmt.Errorf("Unable to parse the field %s: %v", key, err)
}
if hasAnchor, str := hasExistingAnchor(key); hasAnchor {
if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() != reflect.Slice {
return path, fmt.Errorf("existing anchor at %s must be of type array, found: %T", path+str, patternElement)
// check the type of anchor
if matched {
// some type of anchor
// check if valid anchor
if !checkAnchors(key, supportedAnchors) {
return path + "/" + key, fmt.Errorf("Unsupported anchor %s", key)
}
// addition check for existance anchor
// value must be of type list
if anchor.IsExistanceAnchor(key) {
typedValue, ok := value.([]interface{})
if !ok {
return path + "/" + key, fmt.Errorf("Existance anchor should have value of type list")
}
// validate there is only one entry in the list
if len(typedValue) == 0 || len(typedValue) > 1 {
return path + "/" + key, fmt.Errorf("Existance anchor: single value expected, multiple specified")
}
}
}
if path, err := validateAnchors(anchorPatterns, patternElement, path+key+"/"); err != nil {
return path, err
// lets validate the values now :)
if errPath, err := validatePattern(value, path+"/"+key, supportedAnchors); err != nil {
return errPath, err
}
}
return "", nil
}
func validateAnchorsOnArray(anchorPatterns []anchor, patternArray []interface{}, path string) (string, error) {
if len(patternArray) == 0 {
return path, fmt.Errorf("pattern array at %s is empty", path)
}
for i, pattern := range patternArray {
func validateArray(patternArray []interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
if path, err := validateAnchors(anchorPatterns, pattern, currentPath); err != nil {
return path, err
// lets validate the values now :)
if errPath, err := validatePattern(patternElement, currentPath, supportedAnchors); err != nil {
return errPath, err
}
}
return "", nil
}
func checkAnchors(key string, supportedAnchors []anchor.IsAnchor) bool {
for _, f := range supportedAnchors {
if f(key) {
return true
}
}
return false
}

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@ import (
"github.com/minio/minio/pkg/wildcard"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -209,11 +210,12 @@ func ParseNamespaceFromObject(bytes []byte) string {
return ""
}
// Validation
func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for key, value := range anchorsMap {
if isConditionAnchor(key) || isExistanceAnchor(key) {
if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) {
result[key] = value
}
}
@ -221,13 +223,14 @@ func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{}
return result
}
// Mutation
func getElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := make(map[string]interface{})
elementsWithoutanchor := make(map[string]interface{})
for key, value := range anchorsMap {
if isConditionAnchor(key) || isExistanceAnchor(key) {
if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) {
anchors[key] = value
} else if !isAddingAnchor(key) {
} else if !anchor.IsAddingAnchor(key) {
elementsWithoutanchor[key] = value
}
}
@ -237,7 +240,7 @@ func getElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface
func getAnchorFromMap(anchorsMap map[string]interface{}) (string, interface{}) {
for key, value := range anchorsMap {
if isConditionAnchor(key) || isExistanceAnchor(key) {
if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) {
return key, value
}
}
@ -254,13 +257,13 @@ func findKind(kinds []string, kindGVK string) bool {
return false
}
func isConditionAnchor(str string) bool {
if len(str) < 2 {
return false
}
// func isConditionAnchor(str string) bool {
// if len(str) < 2 {
// return false
// }
return (str[0] == '(' && str[len(str)-1] == ')')
}
// return (str[0] == '(' && str[len(str)-1] == ')')
// }
func getRawKeyIfWrappedWithAttributes(str string) string {
if len(str) < 2 {
@ -284,48 +287,6 @@ func isStringIsReference(str string) bool {
return str[0] == '$' && str[1] == '(' && str[len(str)-1] == ')'
}
func isExistanceAnchor(str string) bool {
left := "^("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
func isEqualityAnchor(str string) bool {
left := "=("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
//TODO: trim spaces ?
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
func isNegationAnchor(str string) bool {
left := "X("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
//TODO: trim spaces ?
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
func isAddingAnchor(key string) bool {
const left = "+("
const right = ")"
if len(key) < len(left)+len(right) {
return false
}
return left == key[:len(left)] && right == key[len(key)-len(right):]
}
// Checks if array object matches anchors. If not - skip - return true
func skipArrayObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
@ -346,11 +307,11 @@ func skipArrayObject(object, anchors map[string]interface{}) bool {
// removeAnchor remove special characters around anchored key
func removeAnchor(key string) string {
if isConditionAnchor(key) {
if anchor.IsConditionAnchor(key) {
return key[1 : len(key)-1]
}
if isExistanceAnchor(key) || isAddingAnchor(key) || isEqualityAnchor(key) || isNegationAnchor(key) {
if anchor.IsExistanceAnchor(key) || anchor.IsAddingAnchor(key) || anchor.IsEqualityAnchor(key) || anchor.IsNegationAnchor(key) {
return key[2 : len(key)-1]
}

View file

@ -392,57 +392,6 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
assert.Assert(t, !MatchesResourceDescription(*resource, rule))
}
func TestWrappedWithParentheses_StringIsWrappedWithParentheses(t *testing.T) {
str := "(something)"
assert.Assert(t, isConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasOnlyParentheses(t *testing.T) {
str := "()"
assert.Assert(t, isConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasNoParentheses(t *testing.T) {
str := "something"
assert.Assert(t, !isConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasLeftParentheses(t *testing.T) {
str := "(something"
assert.Assert(t, !isConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasRightParentheses(t *testing.T) {
str := "something)"
assert.Assert(t, !isConditionAnchor(str))
}
func TestWrappedWithParentheses_StringParenthesesInside(t *testing.T) {
str := "so)m(et(hin)g"
assert.Assert(t, !isConditionAnchor(str))
}
func TestWrappedWithParentheses_Empty(t *testing.T) {
str := ""
assert.Assert(t, !isConditionAnchor(str))
}
func TestIsExistanceAnchor_Yes(t *testing.T) {
assert.Assert(t, isExistanceAnchor("^(abc)"))
}
func TestIsExistanceAnchor_NoRightBracket(t *testing.T) {
assert.Assert(t, !isExistanceAnchor("^(abc"))
}
func TestIsExistanceAnchor_OnlyHat(t *testing.T) {
assert.Assert(t, !isExistanceAnchor("^abc"))
}
func TestIsExistanceAnchor_ConditionAnchor(t *testing.T) {
assert.Assert(t, !isExistanceAnchor("(abc)"))
}
func TestRemoveAnchor_ConditionAnchor(t *testing.T) {
assert.Equal(t, removeAnchor("(abc)"), "abc")
}

View file

@ -11,6 +11,7 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -41,10 +42,9 @@ func Validate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured)
}
for _, rule := range policy.Spec.Rules {
if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
if !rule.HasValidate() {
continue
}
// check if the resource satisfies the filter conditions defined in the rule
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
@ -201,7 +201,7 @@ func validateMap(resourceMap, patternMap map[string]interface{}, origPattern int
// 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 isConditionAnchor(key) {
if anchor.IsConditionAnchor(key) {
glog.V(4).Infof("condition anchor did not satisfy, wont process the resources: %s", err)
return "", nil
}