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:
parent
68c87a09ec
commit
3fa8834b4a
13 changed files with 708 additions and 895 deletions
|
@ -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
|
||||
}
|
||||
|
|
54
pkg/engine/anchor/utils.go
Normal file
54
pkg/engine/anchor/utils.go
Normal 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)
|
||||
}
|
58
pkg/engine/anchor/utils_test.go
Normal file
58
pkg/engine/anchor/utils_test.go
Normal 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)"))
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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]
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue