1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-15 17:51:20 +00:00
kyverno/pkg/engine/validation.go

305 lines
7.3 KiB
Go

package engine
import (
"encoding/json"
"fmt"
"log"
"strconv"
"strings"
"github.com/minio/minio/pkg/wildcard"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Operator is string alias that represents selection operators enum
type Operator string
const (
MoreEqual Operator = ">="
LessEqual Operator = "<="
NotEqual Operator = "!="
More Operator = ">"
Less Operator = "<"
)
// TODO: Refactor using State pattern
// TODO: Return Events and pass all checks to get all validation errors (not )
// Validate handles validating admission request
// Checks the target resourse for rules defined in the policy
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) error {
var resource interface{}
json.Unmarshal(rawResource, &resource)
for _, rule := range policy.Spec.Rules {
if rule.Validation == nil {
continue
}
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
if !ok {
log.Printf("Rule \"%s\" is not applicable to resource\n", rule.Name)
continue
}
if err := validateMap(resource, rule.Validation.Pattern); err != nil {
message := *rule.Validation.Message
if len(message) == 0 {
message = fmt.Sprintf("%v", err)
} else {
message = fmt.Sprintf("%s, %s", message, err.Error())
}
return fmt.Errorf("%s: %s", *rule.Validation.Message, err.Error())
}
}
return nil
}
func validateMap(resourcePart, patternPart interface{}) error {
pattern, ok := patternPart.(map[string]interface{})
if !ok {
return fmt.Errorf("expected map, found %T", patternPart)
}
resource, ok := resourcePart.(map[string]interface{})
if !ok {
return fmt.Errorf("expected map, found %T", resourcePart)
}
for key, value := range pattern {
if wrappedWithParentheses(key) {
key = key[1 : len(key)-1]
}
if err := validateMapElement(resource[key], value); err != nil {
return err
}
}
return nil
}
func validateArray(resourcePart, patternPart interface{}) error {
patternArray, ok := patternPart.([]interface{})
if !ok {
return fmt.Errorf("expected array, found %T", patternPart)
}
resourceArray, ok := resourcePart.([]interface{})
if !ok {
return fmt.Errorf("expected array, found %T", resourcePart)
}
switch pattern := patternArray[0].(type) {
case map[string]interface{}:
anchors := GetAnchorsFromMap(pattern)
for _, value := range resourceArray {
resource, ok := value.(map[string]interface{})
if !ok {
return fmt.Errorf("expected array, found %T", resourcePart)
}
if skipValidatingObject(resource, anchors) {
continue
}
if err := validateMap(resource, pattern); err != nil {
return err
}
}
default:
for _, value := range resourceArray {
if err := checkSingleValue(value, patternArray[0]); err != nil {
return err
}
}
}
return nil
}
func validateMapElement(resourcePart, patternPart interface{}) error {
switch pattern := patternPart.(type) {
case map[string]interface{}:
dictionary, ok := resourcePart.(map[string]interface{})
if !ok {
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
}
return validateMap(dictionary, pattern)
case []interface{}:
array, ok := resourcePart.([]interface{})
if !ok {
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
}
return validateArray(array, pattern)
case string:
return checkSingleValue(resourcePart, patternPart)
case float64:
switch num := resourcePart.(type) {
case float64:
if num != pattern {
return fmt.Errorf("%f not equal %f", num, pattern)
}
case int64:
if float64(num) != pattern {
return fmt.Errorf("%d not equal %f", num, pattern)
}
default:
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
}
case int64:
switch num := resourcePart.(type) {
case float64:
if num != float64(pattern) {
return fmt.Errorf("%f not equal %d", num, pattern)
}
case int64:
if float64(num) != float64(num) {
return fmt.Errorf("%d not equal %d", num, pattern)
}
default:
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
}
default:
return fmt.Errorf("validating error: unknown type in map: %T", patternPart)
}
return nil
}
func skipValidatingObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
key = key[1 : len(key)-1]
value, ok := object[key]
if !ok {
return true
}
if err := checkSingleValue(value, pattern); err != nil {
return true
}
}
return false
}
func checkSingleValue(value, pattern interface{}) error {
switch typedPattern := pattern.(type) {
case string:
switch typedValue := value.(type) {
case string:
return checkForWildcard(typedValue, typedPattern)
case float64:
return checkForOperator(typedValue, typedPattern)
case int:
return checkForOperator(float64(typedValue), typedPattern)
default:
return fmt.Errorf("expected string or numerical type, found %T, pattern: %s", value, typedPattern)
}
case float64:
num, ok := value.(float64)
if !ok {
return fmt.Errorf("expected float, found %T", value)
}
if typedPattern != num {
return fmt.Errorf("value %f is not equal to pattern %f", value, typedPattern)
}
case int:
num, ok := value.(int)
if !ok {
return fmt.Errorf("expected int, found %T", value)
}
if typedPattern != num {
return fmt.Errorf("value %d is not equal to pattern %d", num, typedPattern)
}
default:
return fmt.Errorf("expected pattern (string or numerical type), found %T", pattern)
}
return nil
}
func checkForWildcard(value, pattern string) error {
if !wildcard.Match(pattern, value) {
return fmt.Errorf("wildcard check has failed. Pattern: \"%s\". Value: \"%s\"", pattern, value)
}
return nil
}
func checkForOperator(value float64, pattern string) error {
operators := strings.Split(pattern, "|")
for _, operator := range operators {
operator = strings.Replace(operator, " ", "", -1)
// At least one success - return nil
if checkSingleOperator(value, operator) {
return nil
}
}
return fmt.Errorf("operator check has failed. Pattern: \"%s\". Value: \"%f\"", pattern, value)
}
func checkSingleOperator(value float64, pattern string) bool {
if operatorVal, err := strconv.ParseFloat(pattern, 64); err == nil {
return value == operatorVal
}
if len(pattern) < 2 {
fmt.Printf("Validating error: operator can't have less than 2 characters: %s\n", pattern)
return false
}
if operatorVal, ok := parseOperator(MoreEqual, pattern); ok {
return value >= operatorVal
}
if operatorVal, ok := parseOperator(LessEqual, pattern); ok {
return value <= operatorVal
}
if operatorVal, ok := parseOperator(More, pattern); ok {
return value > operatorVal
}
if operatorVal, ok := parseOperator(Less, pattern); ok {
return value < operatorVal
}
if operatorVal, ok := parseOperator(NotEqual, pattern); ok {
return value != operatorVal
}
fmt.Printf("Validating error: unknown operator: %s\n", pattern)
return false
}
func parseOperator(operator Operator, pattern string) (float64, bool) {
if pattern[:len(operator)] == string(operator) {
if value, err := strconv.ParseFloat(pattern[len(operator):len(pattern)], 64); err == nil {
return value, true
}
}
return 0.0, false
}
func wrappedWithParentheses(str string) bool {
if len(str) < 2 {
return false
}
return (str[0] == '(' && str[len(str)-1] == ')')
}