2019-09-27 16:31:27 -07:00
|
|
|
package v1alpha1
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
2019-09-27 19:03:55 -07:00
|
|
|
"strconv"
|
2019-09-27 16:31:27 -07:00
|
|
|
|
2019-09-27 19:03:55 -07:00
|
|
|
"github.com/golang/glog"
|
2019-09-27 16:31:27 -07:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
)
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
type anchor struct {
|
|
|
|
left string
|
|
|
|
right string
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
conditionalAnchor = anchor{left: "(", right: ")"}
|
|
|
|
existingAnchor = anchor{left: "^(", right: ")"}
|
|
|
|
equalityAnchor = anchor{left: "=(", right: ")"}
|
|
|
|
plusAnchor = anchor{left: "+(", right: ")"}
|
|
|
|
)
|
|
|
|
|
2019-09-27 16:31:27 -07:00
|
|
|
func (p ClusterPolicy) Validate() error {
|
|
|
|
var errs []error
|
|
|
|
|
2019-10-03 17:53:46 -07:00
|
|
|
if err := p.validateUniqueRuleName(); err != nil {
|
2019-10-03 16:49:41 -07:00
|
|
|
errs = append(errs, fmt.Errorf("- Invalid Policy '%s':", p.Name))
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
|
2019-09-27 16:31:27 -07:00
|
|
|
for _, rule := range p.Spec.Rules {
|
2019-10-03 17:53:46 -07:00
|
|
|
if ruleErrs := rule.validate(); len(ruleErrs) != 0 {
|
2019-10-03 16:49:41 -07:00
|
|
|
errs = append(errs, fmt.Errorf("- invalid rule '%s':", rule.Name))
|
2019-09-27 19:03:55 -07:00
|
|
|
errs = append(errs, ruleErrs...)
|
|
|
|
}
|
2019-09-27 16:31:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return joinErrs(errs)
|
|
|
|
}
|
|
|
|
|
2019-09-27 19:03:55 -07:00
|
|
|
// ValidateUniqueRuleName checks if the rule names are unique across a policy
|
2019-10-03 17:53:46 -07:00
|
|
|
func (p ClusterPolicy) validateUniqueRuleName() error {
|
2019-09-27 19:03:55 -07:00
|
|
|
var ruleNames []string
|
|
|
|
|
|
|
|
for _, rule := range p.Spec.Rules {
|
|
|
|
if containString(ruleNames, rule.Name) {
|
|
|
|
return fmt.Errorf(`duplicate rule name: '%s'`, rule.Name)
|
|
|
|
}
|
|
|
|
ruleNames = append(ruleNames, rule.Name)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-27 16:31:27 -07:00
|
|
|
// Validate checks if rule is not empty and all substructures are valid
|
2019-10-03 17:53:46 -07:00
|
|
|
func (r Rule) validate() []error {
|
2019-09-27 16:31:27 -07:00
|
|
|
var errs []error
|
|
|
|
|
|
|
|
// only one type of rule is allowed per rule
|
2019-10-03 17:53:46 -07:00
|
|
|
if err := r.validateRuleType(); err != nil {
|
2019-09-27 16:31:27 -07:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// validate resource description block
|
2019-10-03 18:19:47 -07:00
|
|
|
if err := validateMatchedResourceDescription(r.MatchResources.ResourceDescription); err != nil {
|
2019-10-03 16:49:41 -07:00
|
|
|
errs = append(errs, fmt.Errorf("error in match block, %v", err))
|
2019-09-27 16:31:27 -07:00
|
|
|
}
|
|
|
|
|
2019-10-03 18:19:47 -07:00
|
|
|
if err := validateResourceDescription(r.ExcludeResources.ResourceDescription); err != nil {
|
2019-10-03 16:49:41 -07:00
|
|
|
errs = append(errs, fmt.Errorf("error in exclude block, %v", err))
|
2019-09-27 16:31:27 -07:00
|
|
|
}
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
// validate anchors on mutate
|
2019-10-03 17:53:46 -07:00
|
|
|
if mErrs := r.Mutation.validate(); len(mErrs) != 0 {
|
2019-10-03 12:52:58 -07:00
|
|
|
errs = append(errs, mErrs...)
|
2019-09-27 16:31:27 -07:00
|
|
|
}
|
|
|
|
|
2019-10-03 17:53:46 -07:00
|
|
|
if vErrs := r.Validation.validate(); len(vErrs) != 0 {
|
2019-10-03 12:52:58 -07:00
|
|
|
errs = append(errs, vErrs...)
|
2019-09-27 19:03:55 -07:00
|
|
|
}
|
|
|
|
|
2019-10-03 17:53:46 -07:00
|
|
|
if err := r.Generation.validate(); err != nil {
|
2019-10-03 14:47:50 -07:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
2019-09-27 16:31:27 -07:00
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
return errs
|
2019-09-27 16:31:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// validateRuleType checks only one type of rule is defined per rule
|
2019-10-03 17:53:46 -07:00
|
|
|
func (r Rule) validateRuleType() error {
|
|
|
|
ruleTypes := []bool{r.hasMutate(), r.hasValidate(), r.hasGenerate()}
|
2019-09-27 16:31:27 -07:00
|
|
|
|
2019-10-03 16:49:41 -07:00
|
|
|
operationCount := func() int {
|
|
|
|
count := 0
|
|
|
|
for _, v := range ruleTypes {
|
|
|
|
if v {
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}()
|
2019-09-27 16:31:27 -07:00
|
|
|
|
2019-10-03 16:49:41 -07:00
|
|
|
if operationCount == 0 {
|
|
|
|
return fmt.Errorf("no operation defined in the rule '%s'.(supported operations: mutation,validation,generation,query)", 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)
|
2019-09-27 16:31:27 -07:00
|
|
|
}
|
2019-10-03 16:49:41 -07:00
|
|
|
return nil
|
2019-09-27 16:31:27 -07:00
|
|
|
}
|
|
|
|
|
2019-10-03 18:19:47 -07:00
|
|
|
// validateResourceDescription checks if all necesarry fields are present and have values. Also checks a Selector.
|
2019-09-27 16:31:27 -07:00
|
|
|
// field type is checked through openapi
|
|
|
|
// Returns error if
|
2019-10-01 15:01:24 -07:00
|
|
|
// - kinds is empty array in matched resource block, i.e. kinds: []
|
2019-09-27 16:31:27 -07:00
|
|
|
// - selector is invalid
|
2019-10-03 18:19:47 -07:00
|
|
|
func validateMatchedResourceDescription(rd ResourceDescription) error {
|
2019-09-27 16:31:27 -07:00
|
|
|
if reflect.DeepEqual(rd, ResourceDescription{}) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-03 18:19:47 -07:00
|
|
|
if len(rd.Kinds) == 0 {
|
2019-09-27 16:31:27 -07:00
|
|
|
return errors.New("field Kind is not specified")
|
|
|
|
}
|
|
|
|
|
2019-10-03 18:19:47 -07:00
|
|
|
return validateResourceDescription(rd)
|
|
|
|
}
|
|
|
|
|
|
|
|
// validateResourceDescription returns error if selector is invalid
|
|
|
|
// field type is checked through openapi
|
|
|
|
func validateResourceDescription(rd ResourceDescription) error {
|
2019-09-27 16:31:27 -07:00
|
|
|
if rd.Selector != nil {
|
|
|
|
selector, err := metav1.LabelSelectorAsSelector(rd.Selector)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
requirements, _ := selector.Requirements()
|
|
|
|
if len(requirements) == 0 {
|
|
|
|
return errors.New("the requirements are not specified in selector")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-03 17:53:46 -07:00
|
|
|
func (m Mutation) validate() []error {
|
2019-10-03 12:52:58 -07:00
|
|
|
var errs []error
|
|
|
|
if len(m.Patches) != 0 {
|
|
|
|
for _, patch := range m.Patches {
|
2019-10-03 17:53:46 -07:00
|
|
|
err := patch.validate()
|
2019-10-03 12:52:58 -07:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.Overlay != nil {
|
|
|
|
_, err := validateAnchors([]anchor{conditionalAnchor, plusAnchor}, m.Overlay, "/")
|
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
|
2019-09-27 16:31:27 -07:00
|
|
|
// Validate if all mandatory PolicyPatch fields are set
|
2019-10-03 17:53:46 -07:00
|
|
|
func (pp *Patch) validate() error {
|
2019-09-27 16:31:27 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
} else if pp.Operation == "remove" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
|
|
|
|
}
|
|
|
|
|
2019-10-03 17:53:46 -07:00
|
|
|
func (v Validation) validate() []error {
|
2019-09-27 19:03:55 -07:00
|
|
|
var errs []error
|
|
|
|
|
2019-10-03 17:53:46 -07:00
|
|
|
if err := v.validateOverlayPattern(); err != nil {
|
2019-10-03 12:52:58 -07:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if v.Pattern != nil {
|
|
|
|
if _, err := validateAnchors([]anchor{conditionalAnchor, existingAnchor, equalityAnchor}, v.Pattern, "/"); err != nil {
|
2019-09-27 19:03:55 -07:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
if len(v.AnyPattern) != 0 {
|
|
|
|
for _, p := range v.AnyPattern {
|
|
|
|
if _, err := validateAnchors([]anchor{conditionalAnchor, existingAnchor, equalityAnchor}, p, "/"); err != nil {
|
2019-09-27 19:03:55 -07:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
// validateOverlayPattern checks one of pattern/anyPattern must exist
|
2019-10-03 17:53:46 -07:00
|
|
|
func (v Validation) validateOverlayPattern() error {
|
2019-10-03 12:52:58 -07:00
|
|
|
if reflect.DeepEqual(v, Validation{}) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if v.Pattern == nil && len(v.AnyPattern) == 0 {
|
|
|
|
return fmt.Errorf("neither pattern nor anyPattern found")
|
|
|
|
}
|
|
|
|
|
|
|
|
if v.Pattern != nil && len(v.AnyPattern) != 0 {
|
|
|
|
return fmt.Errorf("either pattern or anyPattern is allowed")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate returns error if generator is configured incompletely
|
2019-10-03 17:53:46 -07:00
|
|
|
func (gen Generation) validate() error {
|
2019-10-03 14:47:50 -07:00
|
|
|
if reflect.DeepEqual(gen, Generation{}) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
if gen.Data == nil && gen.Clone == (CloneFrom{}) {
|
2019-10-03 14:47:50 -07:00
|
|
|
return fmt.Errorf("neither data nor clone (source) of %s is specified", gen.Kind)
|
2019-10-03 12:52:58 -07:00
|
|
|
}
|
|
|
|
if gen.Data != nil && gen.Clone != (CloneFrom{}) {
|
2019-10-03 14:47:50 -07:00
|
|
|
return fmt.Errorf("both data nor clone (source) of %s are specified", gen.Kind)
|
2019-10-03 12:52:58 -07:00
|
|
|
}
|
2019-10-03 14:47:50 -07:00
|
|
|
|
2019-10-03 16:49:41 -07:00
|
|
|
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)
|
|
|
|
}
|
2019-10-03 14:47:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(gen.Clone, CloneFrom{}) {
|
2019-10-03 16:49:41 -07:00
|
|
|
if _, err := validateAnchors(nil, gen.Clone, ""); err != nil {
|
|
|
|
return fmt.Errorf("invalid character found on pattern clone: %v", err)
|
2019-10-03 14:47:50 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
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: (), ^(), =()
|
|
|
|
// 4. no anchor is allowed in generate
|
|
|
|
func validateAnchors(anchorPatterns []anchor, pattern interface{}, path string) (string, error) {
|
2019-09-27 19:03:55 -07:00
|
|
|
switch typedPattern := pattern.(type) {
|
|
|
|
case map[string]interface{}:
|
2019-10-03 12:52:58 -07:00
|
|
|
return validateAnchorsOnMap(anchorPatterns, typedPattern, path)
|
2019-09-27 19:03:55 -07:00
|
|
|
case []interface{}:
|
2019-10-03 12:52:58 -07:00
|
|
|
return validateAnchorsOnArray(anchorPatterns, typedPattern, path)
|
2019-09-27 19:03:55 -07:00
|
|
|
case string, float64, int, int64, bool, nil:
|
|
|
|
// check on type string
|
|
|
|
if checkedPattern := reflect.ValueOf(pattern); checkedPattern.Kind() == reflect.String {
|
2019-10-01 12:41:10 -07:00
|
|
|
if hasAnchor, str := hasExistingAnchor(checkedPattern.String()); hasAnchor {
|
2019-10-03 12:52:58 -07:00
|
|
|
return path, fmt.Errorf("existing anchor at %s must be of type array, found: %v", path+str, checkedPattern.Kind())
|
2019-09-27 19:03:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// return nil on all other cases
|
|
|
|
return "", nil
|
2019-10-03 16:49:41 -07:00
|
|
|
case interface{}:
|
|
|
|
// special case for generate clone, as it is a struct
|
|
|
|
if clone, ok := pattern.(CloneFrom); ok {
|
|
|
|
return "", validateAnchorsOnCloneFrom(nil, clone)
|
|
|
|
}
|
|
|
|
return "", nil
|
2019-09-27 19:03:55 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-03 16:49:41 -07:00
|
|
|
func validateAnchorsOnCloneFrom(anchorPatterns []anchor, pattern 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
|
|
|
|
}
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
func validateAnchorsOnMap(anchorPatterns []anchor, pattern map[string]interface{}, path string) (string, error) {
|
2019-09-27 19:03:55 -07:00
|
|
|
for key, patternElement := range pattern {
|
2019-10-03 12:52:58 -07:00
|
|
|
if valid, str := hasValidAnchors(anchorPatterns, key); !valid {
|
|
|
|
return path, fmt.Errorf("invalid anchor found at %s, expect: %s", path+str, joinAnchors(anchorPatterns))
|
|
|
|
}
|
2019-10-01 12:41:10 -07:00
|
|
|
if hasAnchor, str := hasExistingAnchor(key); hasAnchor {
|
2019-09-27 19:03:55 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
if path, err := validateAnchors(anchorPatterns, patternElement, path+key+"/"); err != nil {
|
2019-09-27 19:03:55 -07:00
|
|
|
return path, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2019-10-03 12:52:58 -07:00
|
|
|
func validateAnchorsOnArray(anchorPatterns []anchor, patternArray []interface{}, path string) (string, error) {
|
2019-09-27 19:03:55 -07:00
|
|
|
if len(patternArray) == 0 {
|
|
|
|
return path, fmt.Errorf("pattern array at %s is empty", path)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, pattern := range patternArray {
|
|
|
|
currentPath := path + strconv.Itoa(i) + "/"
|
2019-10-03 12:52:58 -07:00
|
|
|
if path, err := validateAnchors(anchorPatterns, pattern, currentPath); err != nil {
|
2019-09-27 19:03:55 -07:00
|
|
|
return path, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|