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" "strconv"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/anchor"
) )
//ValidationHandler for element processes //ValidationHandler for element processes
@ -15,19 +16,20 @@ type ValidationHandler interface {
//CreateElementHandler factory to process elements //CreateElementHandler factory to process elements
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler { func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
switch { switch {
case isConditionAnchor(element): case anchor.IsConditionAnchor(element):
return NewConditionAnchorHandler(element, pattern, path) return NewConditionAnchorHandler(element, pattern, path)
case isExistanceAnchor(element): case anchor.IsExistanceAnchor(element):
return NewExistanceHandler(element, pattern, path) return NewExistanceHandler(element, pattern, path)
case isEqualityAnchor(element): case anchor.IsEqualityAnchor(element):
return NewEqualityHandler(element, pattern, path) return NewEqualityHandler(element, pattern, path)
case isNegationAnchor(element): case anchor.IsNegationAnchor(element):
return NewNegationHandler(element, pattern, path) return NewNegationHandler(element, pattern, path)
default: default:
return NewDefaultHandler(element, pattern, path) return NewDefaultHandler(element, pattern, path)
} }
} }
//NewNegationHandler returns instance of negation handler
func NewNegationHandler(anchor string, pattern interface{}, path string) ValidationHandler { func NewNegationHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return NegationHandler{ return NegationHandler{
anchor: anchor, anchor: anchor,
@ -56,6 +58,7 @@ func (nh NegationHandler) Handle(resourceMap map[string]interface{}, originPatte
return "", nil return "", nil
} }
//NewEqualityHandler returens instance of equality handler
func NewEqualityHandler(anchor string, pattern interface{}, path string) ValidationHandler { func NewEqualityHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return EqualityHandler{ return EqualityHandler{
anchor: anchor, anchor: anchor,
@ -217,7 +220,7 @@ func getAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]i
anchors := map[string]interface{}{} anchors := map[string]interface{}{}
resources := map[string]interface{}{} resources := map[string]interface{}{}
for key, value := range patternMap { 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 anchors[key] = value
continue 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++ response.PolicyResponse.RulesAppliedCount++
} }
for _, rule := range policy.Spec.Rules { for _, rule := range policy.Spec.Rules {
if rule.Generation == (kyverno.Generation{}) { if !rule.HasGenerate() {
continue 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()) 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 { for _, rule := range policy.Spec.Rules {
//TODO: to be checked before calling the resources as well //TODO: to be checked before calling the resources as well
if reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) { if !rule.HasMutate() {
continue continue
} }
// check if the resource satisfies the filter conditions defined in the rule // 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" jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
) )
// processOverlay processes validation patterns on the resource // 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 { for key, value := range overlayMap {
// skip anchor element because it has condition, not // skip anchor element because it has condition, not
// the value that must replace resource value // the value that must replace resource value
if isConditionAnchor(key) { if anchor.IsConditionAnchor(key) {
continue continue
} }
@ -156,7 +157,7 @@ func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path stri
currentPath := path + noAnchorKey + "/" currentPath := path + noAnchorKey + "/"
resourcePart, ok := resourceMap[noAnchorKey] resourcePart, ok := resourceMap[noAnchorKey]
if ok && !isAddingAnchor(key) { if ok && !anchor.IsAddingAnchor(key) {
// Key exists - go down through the overlay and resource trees // Key exists - go down through the overlay and resource trees
patches, err := applyOverlay(resourcePart, value, currentPath) patches, err := applyOverlay(resourcePart, value, currentPath)
if err != nil { if err != nil {

View file

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

View file

@ -1,26 +1,5 @@
package policy 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 //Contains Check if strint is contained in a list of string
func containString(list []string, element string) bool { func containString(list []string, element string) bool {
for _, e := range list { for _, e := range list {
@ -30,73 +9,3 @@ func containString(list []string, element string) bool {
} }
return false 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" "errors"
"fmt" "fmt"
"reflect" "reflect"
"regexp"
"strconv" "strconv"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type anchor struct { // Validate does some initial check to verify some conditions
left string // - One operation per rule
right string // - ResourceDescription mandatory checks
}
var (
conditionalAnchor = anchor{left: "(", right: ")"}
existingAnchor = anchor{left: "^(", right: ")"}
equalityAnchor = anchor{left: "=(", right: ")"}
plusAnchor = anchor{left: "+(", right: ")"}
negationAnchor = anchor{left: "X(", right: ")"}
)
func Validate(p kyverno.ClusterPolicy) error { func Validate(p kyverno.ClusterPolicy) error {
var errs []error if path, err := validateUniqueRuleName(p); err != nil {
return fmt.Errorf("path: spec.%s: %v", path, err)
if err := validateUniqueRuleName(p); err != nil {
errs = append(errs, fmt.Errorf("- Invalid Policy '%s':", p.Name))
errs = append(errs, err)
} }
for _, rule := range p.Spec.Rules { for i, rule := range p.Spec.Rules {
if ruleErrs := validateRule(rule); len(ruleErrs) != 0 { // only one type of rule is allowed per rule
errs = append(errs, fmt.Errorf("- invalid rule '%s':", rule.Name)) if err := validateRuleType(rule); err != nil {
errs = append(errs, ruleErrs...) return fmt.Errorf("path: spec.rules[%d]: %v", i, err)
} }
}
return joinErrs(errs) // validate resource description
} if path, err := validateResources(rule); err != nil {
return fmt.Errorf("path: spec.rules[%d].%s: %v", i, path, err)
// ValidateUniqueRuleName checks if the rule names are unique across a policy }
func validateUniqueRuleName(p kyverno.ClusterPolicy) error { // validate rule types
var ruleNames []string // only one type of rule is allowed per rule
if err := validateRuleType(rule); err != nil {
for _, rule := range p.Spec.Rules { // as there are more than 1 operation in rule, not need to evaluate it further
if containString(ruleNames, rule.Name) { return fmt.Errorf("path: spec.rules[%d]: %v", i, err)
return fmt.Errorf(`duplicate rule name: '%s'`, rule.Name) }
// 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 return nil
} }
// Validate checks if rule is not empty and all substructures are valid func validateResources(rule kyverno.Rule) (string, error) {
func validateRule(r kyverno.Rule) []error { // matched resources
var errs []error if path, err := validateMatchedResourceDescription(rule.MatchResources.ResourceDescription); err != nil {
return fmt.Sprintf("resources.%s", path), err
// only one type of rule is allowed per rule
if err := validateRuleType(r); err != nil {
errs = append(errs, err)
} }
// exclude resources
// validate resource description block if path, err := validateExcludeResourceDescription(rule.ExcludeResources.ResourceDescription); err != nil {
if err := validateMatchedResourceDescription(r.MatchResources.ResourceDescription); err != nil { return fmt.Sprintf("resources.%s", path), err
errs = append(errs, fmt.Errorf("error in match block, %v", err))
} }
return "", nil
}
if err := validateResourceDescription(r.ExcludeResources.ResourceDescription); err != nil { // ValidateUniqueRuleName checks if the rule names are unique across a policy
errs = append(errs, fmt.Errorf("error in exclude block, %v", err)) 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)
} }
return "", nil
// 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
} }
// validateRuleType checks only one type of rule is defined per rule // validateRuleType checks only one type of rule is defined per rule
@ -104,7 +99,7 @@ func validateRuleType(r kyverno.Rule) error {
}() }()
if operationCount == 0 { 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 { } 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) 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 // Returns error if
// - kinds is empty array in matched resource block, i.e. kinds: [] // - kinds is empty array in matched resource block, i.e. kinds: []
// - selector is invalid // - selector is invalid
func validateMatchedResourceDescription(rd kyverno.ResourceDescription) error { func validateMatchedResourceDescription(rd kyverno.ResourceDescription) (string, error) {
if reflect.DeepEqual(rd, kyverno.ResourceDescription{}) { if reflect.DeepEqual(rd, kyverno.ResourceDescription{}) {
return nil return "", fmt.Errorf("match resources not specified")
} }
if len(rd.Kinds) == 0 { 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 // validateResourceDescription returns error if selector is invalid
@ -144,22 +154,23 @@ func validateResourceDescription(rd kyverno.ResourceDescription) error {
return nil return nil
} }
func validateMutation(m kyverno.Mutation) []error { func validateMutation(m kyverno.Mutation) (string, error) {
var errs []error // JSON Patches
if len(m.Patches) != 0 { if len(m.Patches) != 0 {
for _, patch := range m.Patches { for i, patch := range m.Patches {
err := validatePatch(patch) if err := validatePatch(patch); err != nil {
errs = append(errs, err) return fmt.Sprintf("patch[%d]", i), err
}
} }
} }
// Overlay
if m.Overlay != nil { 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 { if err != nil {
errs = append(errs, err) return path, err
} }
} }
return errs return "", nil
} }
// Validate if all mandatory PolicyPatch fields are set // Validate if all mandatory PolicyPatch fields are set
@ -167,7 +178,6 @@ func validatePatch(pp kyverno.Patch) error {
if pp.Path == "" { if pp.Path == "" {
return errors.New("JSONPatch field 'path' is mandatory") return errors.New("JSONPatch field 'path' is mandatory")
} }
if pp.Operation == "add" || pp.Operation == "replace" { if pp.Operation == "add" || pp.Operation == "replace" {
if pp.Value == nil { if pp.Value == nil {
return fmt.Errorf("JSONPatch field 'value' is mandatory for operation '%s'", pp.Operation) 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) return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
} }
func validateValidation(v kyverno.Validation) []error { func validateValidation(v kyverno.Validation) (string, error) {
var errs []error
if err := validateOverlayPattern(v); err != nil { if err := validateOverlayPattern(v); err != nil {
errs = append(errs, err) // no need to proceed ahead
return "", err
} }
if v.Pattern != nil { if v.Pattern != nil {
if _, err := validateAnchors([]anchor{conditionalAnchor, existingAnchor, equalityAnchor, negationAnchor}, v.Pattern, "/"); err != nil { if path, err := validatePattern(v.Pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistanceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
errs = append(errs, err) return fmt.Sprintf("pattern.%s", path), err
} }
} }
if len(v.AnyPattern) != 0 { if len(v.AnyPattern) != 0 {
for _, p := range v.AnyPattern { for i, pattern := range v.AnyPattern {
if _, err := validateAnchors([]anchor{conditionalAnchor, existingAnchor, equalityAnchor, negationAnchor}, p, "/"); err != nil { if path, err := validatePattern(pattern, "/", []anchor.IsAnchor{anchor.IsConditionAnchor, anchor.IsExistanceAnchor, anchor.IsEqualityAnchor, anchor.IsNegationAnchor}); err != nil {
errs = append(errs, err) return fmt.Sprintf("anyPattern[%d].%s", i, path), err
} }
} }
} }
return "", nil
return errs
} }
// validateOverlayPattern checks one of pattern/anyPattern must exist // validateOverlayPattern checks one of pattern/anyPattern must exist
func validateOverlayPattern(v kyverno.Validation) error { func validateOverlayPattern(v kyverno.Validation) error {
if reflect.DeepEqual(v, kyverno.Validation{}) {
return nil
}
if v.Pattern == nil && len(v.AnyPattern) == 0 { 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 { 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 return nil
} }
// Validate returns error if generator is configured incompletely // Validate returns error if generator is configured incompletely
func validateGeneration(gen kyverno.Generation) error { func validateGeneration(gen kyverno.Generation) (string, error) {
if reflect.DeepEqual(gen, kyverno.Generation{}) {
return nil
}
if gen.Data == nil && gen.Clone == (kyverno.CloneFrom{}) { 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{}) { 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)")
} }
// check kind is non empty
if gen.Data != nil { // check name is non empty
if _, err := validateAnchors(nil, gen.Data, "/"); err != nil { if gen.Name == "" {
return fmt.Errorf("anchors are not allowed on generate pattern data: %v", err) 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 !reflect.DeepEqual(gen.Clone, kyverno.CloneFrom{}) {
if _, err := validateAnchors(nil, gen.Clone, ""); err != nil { if path, err := validateClone(gen.Clone); err != nil {
return fmt.Errorf("invalid character found on pattern clone: %v", err) return fmt.Sprintf("clone.%s", path), err
} }
} }
if gen.Data != nil {
return 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: func validateClone(c kyverno.CloneFrom) (string, error) {
// 1. existing acnchor must define on array if c.Name == "" {
// 2. anchors in mutation must be one of: (), +() return "name", fmt.Errorf("name cannot be empty")
// 3. anchors in validate must be one of: (), ^(), =(), X() }
// 4. no anchor is allowed in generate if c.Namespace == "" {
func validateAnchors(anchorPatterns []anchor, pattern interface{}, path string) (string, error) { return "namespace", fmt.Errorf("namespace cannot be empty")
switch typedPattern := pattern.(type) { }
return "", nil
}
func validatePattern(patternElement interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
switch typedPatternElement := patternElement.(type) {
case map[string]interface{}: case map[string]interface{}:
return validateAnchorsOnMap(anchorPatterns, typedPattern, path) return validateMap(typedPatternElement, path, supportedAnchors)
case []interface{}: case []interface{}:
return validateAnchorsOnArray(anchorPatterns, typedPattern, path) return validateArray(typedPatternElement, path, supportedAnchors)
case string, float64, int, int64, bool, nil: case string, float64, int, int64, bool, nil:
// check on type string //TODO? check operator
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)
}
return "", nil return "", nil
default: default:
glog.V(4).Infof("Pattern contains unknown type %T. Path: %s", pattern, path) return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
return path, fmt.Errorf("pattern contains unknown type, path: %s", path)
} }
return "", nil
} }
func validateAnchorsOnCloneFrom(anchorPatterns []anchor, pattern kyverno.CloneFrom) error { func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
// namespace and name are required fields // check if anchors are defined
// if wrapped with invalid character, this field is empty during unmarshaling for key, value := range patternMap {
if pattern.Namespace == "" { // if key is anchor
return errors.New("namespace is requried") // check regex () -> this is anchor
} // ()
// single char ()
if pattern.Name == "" { matched, err := regexp.MatchString(`^.?\(.+\)$`, key)
return errors.New("name is requried") if err != nil {
} return path + "/" + key, fmt.Errorf("Unable to parse the field %s: %v", key, err)
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))
} }
if hasAnchor, str := hasExistingAnchor(key); hasAnchor { // check the type of anchor
if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() != reflect.Slice { if matched {
return path, fmt.Errorf("existing anchor at %s must be of type array, found: %T", path+str, patternElement) // 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")
}
} }
} }
// lets validate the values now :)
if path, err := validateAnchors(anchorPatterns, patternElement, path+key+"/"); err != nil { if errPath, err := validatePattern(value, path+"/"+key, supportedAnchors); err != nil {
return path, err return errPath, err
} }
} }
return "", nil return "", nil
} }
func validateAnchorsOnArray(anchorPatterns []anchor, patternArray []interface{}, path string) (string, error) { func validateArray(patternArray []interface{}, path string, supportedAnchors []anchor.IsAnchor) (string, error) {
if len(patternArray) == 0 { for i, patternElement := range patternArray {
return path, fmt.Errorf("pattern array at %s is empty", path)
}
for i, pattern := range patternArray {
currentPath := path + strconv.Itoa(i) + "/" currentPath := path + strconv.Itoa(i) + "/"
if path, err := validateAnchors(anchorPatterns, pattern, currentPath); err != nil { // lets validate the values now :)
return path, err if errPath, err := validatePattern(patternElement, currentPath, supportedAnchors); err != nil {
return errPath, err
} }
} }
return "", nil 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" "github.com/minio/minio/pkg/wildcard"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/utils" "github.com/nirmata/kyverno/pkg/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -209,11 +210,12 @@ func ParseNamespaceFromObject(bytes []byte) string {
return "" return ""
} }
// Validation
func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} { func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{}) result := make(map[string]interface{})
for key, value := range anchorsMap { for key, value := range anchorsMap {
if isConditionAnchor(key) || isExistanceAnchor(key) { if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) {
result[key] = value result[key] = value
} }
} }
@ -221,13 +223,14 @@ func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{}
return result return result
} }
// Mutation
func getElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) { func getElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := make(map[string]interface{}) anchors := make(map[string]interface{})
elementsWithoutanchor := make(map[string]interface{}) elementsWithoutanchor := make(map[string]interface{})
for key, value := range anchorsMap { for key, value := range anchorsMap {
if isConditionAnchor(key) || isExistanceAnchor(key) { if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) {
anchors[key] = value anchors[key] = value
} else if !isAddingAnchor(key) { } else if !anchor.IsAddingAnchor(key) {
elementsWithoutanchor[key] = value elementsWithoutanchor[key] = value
} }
} }
@ -237,7 +240,7 @@ func getElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface
func getAnchorFromMap(anchorsMap map[string]interface{}) (string, interface{}) { func getAnchorFromMap(anchorsMap map[string]interface{}) (string, interface{}) {
for key, value := range anchorsMap { for key, value := range anchorsMap {
if isConditionAnchor(key) || isExistanceAnchor(key) { if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) {
return key, value return key, value
} }
} }
@ -254,13 +257,13 @@ func findKind(kinds []string, kindGVK string) bool {
return false return false
} }
func isConditionAnchor(str string) bool { // func isConditionAnchor(str string) bool {
if len(str) < 2 { // if len(str) < 2 {
return false // return false
} // }
return (str[0] == '(' && str[len(str)-1] == ')') // return (str[0] == '(' && str[len(str)-1] == ')')
} // }
func getRawKeyIfWrappedWithAttributes(str string) string { func getRawKeyIfWrappedWithAttributes(str string) string {
if len(str) < 2 { if len(str) < 2 {
@ -284,48 +287,6 @@ func isStringIsReference(str string) bool {
return str[0] == '$' && str[1] == '(' && str[len(str)-1] == ')' 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 // Checks if array object matches anchors. If not - skip - return true
func skipArrayObject(object, anchors map[string]interface{}) bool { func skipArrayObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors { for key, pattern := range anchors {
@ -346,11 +307,11 @@ func skipArrayObject(object, anchors map[string]interface{}) bool {
// removeAnchor remove special characters around anchored key // removeAnchor remove special characters around anchored key
func removeAnchor(key string) string { func removeAnchor(key string) string {
if isConditionAnchor(key) { if anchor.IsConditionAnchor(key) {
return key[1 : len(key)-1] 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] 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)) 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) { func TestRemoveAnchor_ConditionAnchor(t *testing.T) {
assert.Equal(t, removeAnchor("(abc)"), "abc") assert.Equal(t, removeAnchor("(abc)"), "abc")
} }

View file

@ -11,6 +11,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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 { for _, rule := range policy.Spec.Rules {
if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) { if !rule.HasValidate() {
continue continue
} }
// check if the resource satisfies the filter conditions defined in the rule // 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 // 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 // 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 // but if there are non then its a if then check
if err != nil { if err != nil {
// If Conditional anchor fails then we dont process the resources // 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) glog.V(4).Infof("condition anchor did not satisfy, wont process the resources: %s", err)
return "", nil return "", nil
} }