mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
305 lines
7.3 KiB
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] == ')')
|
|
}
|