mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
328 lines
8 KiB
Go
328 lines
8 KiB
Go
package engine
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/minio/minio/pkg/wildcard"
|
|
|
|
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
// TODO: This operators are already implemented in kubernetes
|
|
type Operator string
|
|
|
|
const (
|
|
MoreEqual Operator = ">="
|
|
LessEqual Operator = "<="
|
|
NotEqual Operator = "!="
|
|
More Operator = ">"
|
|
Less Operator = "<"
|
|
)
|
|
|
|
// TODO: Refactor using State pattern
|
|
|
|
// 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) bool {
|
|
var resource interface{}
|
|
json.Unmarshal(rawResource, &resource)
|
|
|
|
allowed := true
|
|
for i, rule := range policy.Spec.Rules {
|
|
|
|
// Checks for preconditions
|
|
// TODO: Rework PolicyEngine interface that it receives not a policy, but mutation object for
|
|
// Mutate, validation for Validate and so on. It will allow to bring this checks outside of PolicyEngine
|
|
// to common part as far as they present for all: mutation, validation, generation
|
|
|
|
err := rule.Validate()
|
|
if err != nil {
|
|
log.Printf("Rule has invalid structure: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
|
continue
|
|
}
|
|
|
|
ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
|
|
if err != nil {
|
|
log.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
|
continue
|
|
}
|
|
|
|
if !ok {
|
|
log.Printf("Rule is not applicable to the request: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
|
continue
|
|
}
|
|
|
|
if rule.Validation == nil {
|
|
continue
|
|
}
|
|
|
|
if !validateMap(resource, rule.Validation.Pattern) {
|
|
log.Printf("Validation with the rule %s has failed: %s\n", rule.Name, *rule.Validation.Message)
|
|
allowed = false
|
|
} else {
|
|
log.Printf("Validation rule %s is successful\n", rule.Name)
|
|
}
|
|
}
|
|
|
|
return allowed
|
|
}
|
|
|
|
func validateMap(resourcePart, patternPart interface{}) bool {
|
|
pattern := patternPart.(map[string]interface{})
|
|
resource, ok := resourcePart.(map[string]interface{})
|
|
|
|
if !ok {
|
|
fmt.Printf("Validating error: expected Map, found %T\n", resourcePart)
|
|
return false
|
|
}
|
|
|
|
for key, value := range pattern {
|
|
if wrappedWithParentheses(key) {
|
|
key = key[1 : len(key)-1]
|
|
}
|
|
|
|
if !validateMapElement(resource[key], value) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func validateArray(resourcePart, patternPart interface{}) bool {
|
|
patternArray := patternPart.([]interface{})
|
|
resourceArray, ok := resourcePart.([]interface{})
|
|
|
|
if !ok {
|
|
fmt.Printf("Validating error: expected array, found %T\n", resourcePart)
|
|
return false
|
|
}
|
|
|
|
switch pattern := patternArray[0].(type) {
|
|
case map[string]interface{}:
|
|
anchors, err := getAnchorsFromMap(pattern)
|
|
if err != nil {
|
|
fmt.Printf("Validating error: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
for _, value := range resourceArray {
|
|
resource, ok := value.(map[string]interface{})
|
|
if !ok {
|
|
fmt.Printf("Validating error: expected Map, found %T\n", resourcePart)
|
|
return false
|
|
}
|
|
|
|
if skipArrayObject(resource, anchors) {
|
|
continue
|
|
}
|
|
|
|
if !validateMap(resource, pattern) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
default:
|
|
for _, value := range resourceArray {
|
|
if !checkSingleValue(value, patternArray[0]) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func validateMapElement(resourcePart, patternPart interface{}) bool {
|
|
switch pattern := patternPart.(type) {
|
|
case map[string]interface{}:
|
|
dictionary, ok := resourcePart.(map[string]interface{})
|
|
|
|
if !ok {
|
|
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
|
|
return false
|
|
}
|
|
|
|
return validateMap(dictionary, pattern)
|
|
case []interface{}:
|
|
array, ok := resourcePart.([]interface{})
|
|
|
|
if !ok {
|
|
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
|
|
return false
|
|
}
|
|
|
|
return validateArray(array, pattern)
|
|
case string:
|
|
str, ok := resourcePart.(string)
|
|
|
|
if !ok {
|
|
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
|
|
return false
|
|
}
|
|
|
|
return checkSingleValue(str, pattern)
|
|
default:
|
|
fmt.Printf("Validating error: unknown type in map: %T\n", patternPart)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func getAnchorsFromMap(pattern map[string]interface{}) (map[string]interface{}, error) {
|
|
result := make(map[string]interface{})
|
|
|
|
for key, value := range pattern {
|
|
if wrappedWithParentheses(key) {
|
|
result[key] = value
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func skipArrayObject(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 !checkSingleValue(value, pattern) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func checkSingleValue(value, pattern interface{}) bool {
|
|
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:
|
|
fmt.Printf("Validating error: expected string or numerical type, found %T, pattern: %s\n", value, typedPattern)
|
|
return false
|
|
}
|
|
case float64:
|
|
num, ok := value.(float64)
|
|
if !ok {
|
|
fmt.Printf("Validating error: expected float, found %T\n", value)
|
|
return false
|
|
}
|
|
|
|
return typedPattern == num
|
|
case int:
|
|
num, ok := value.(int)
|
|
if !ok {
|
|
fmt.Printf("Validating error: expected int, found %T\n", value)
|
|
return false
|
|
}
|
|
|
|
return typedPattern == num
|
|
default:
|
|
fmt.Printf("Validating error: expected pattern (string or numerical type), found %T\n", pattern)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func checkForWildcard(value, pattern string) bool {
|
|
return wildcard.Match(pattern, value)
|
|
}
|
|
|
|
func checkSingleOperator(value float64, operator string) bool {
|
|
if operatorVal, err := strconv.ParseFloat(operator, 64); err == nil {
|
|
return value == operatorVal
|
|
}
|
|
|
|
if len(operator) < 2 {
|
|
fmt.Printf("Validating error: operator can't have less than 2 characters: %s\n", operator)
|
|
return false
|
|
}
|
|
|
|
if operator[:len(MoreEqual)] == string(MoreEqual) {
|
|
operatorVal, err := strconv.ParseFloat(operator[len(MoreEqual):len(operator)], 64)
|
|
if err != nil {
|
|
fmt.Printf("Validating error: failed to parse operator value: %s\n", operator)
|
|
return false
|
|
}
|
|
|
|
return value >= operatorVal
|
|
}
|
|
|
|
if operator[:len(LessEqual)] == string(LessEqual) {
|
|
operatorVal, err := strconv.ParseFloat(operator[len(LessEqual):len(operator)], 64)
|
|
if err != nil {
|
|
fmt.Printf("Validating error: failed to parse operator value: %s\n", operator)
|
|
return false
|
|
}
|
|
|
|
return value <= operatorVal
|
|
}
|
|
|
|
if operator[:len(More)] == string(More) {
|
|
operatorVal, err := strconv.ParseFloat(operator[len(More):len(operator)], 64)
|
|
if err != nil {
|
|
fmt.Printf("Validating error: failed to parse operator value: %s\n", operator)
|
|
return false
|
|
}
|
|
|
|
return value > operatorVal
|
|
}
|
|
|
|
if operator[:len(Less)] == string(Less) {
|
|
operatorVal, err := strconv.ParseFloat(operator[len(Less):len(operator)], 64)
|
|
if err != nil {
|
|
fmt.Printf("Validating error: failed to parse operator value: %s\n", operator)
|
|
return false
|
|
}
|
|
|
|
return value < operatorVal
|
|
}
|
|
|
|
if operator[:len(NotEqual)] == string(NotEqual) {
|
|
operatorVal, err := strconv.ParseFloat(operator[len(NotEqual):len(operator)], 64)
|
|
if err != nil {
|
|
fmt.Printf("Validating error: failed to parse operator value: %s\n", operator)
|
|
return false
|
|
}
|
|
|
|
return value != operatorVal
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func checkForOperator(value float64, pattern string) bool {
|
|
operators := strings.Split(pattern, "|")
|
|
|
|
for _, operator := range operators {
|
|
operator = strings.Replace(operator, " ", "", -1)
|
|
if checkSingleOperator(value, operator) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func wrappedWithParentheses(str string) bool {
|
|
if len(str) < 2 {
|
|
return false
|
|
}
|
|
|
|
return (str[0] == '(' && str[len(str)-1] == ')')
|
|
}
|