2019-05-13 18:17:28 -07:00
|
|
|
package engine
|
|
|
|
|
2019-05-14 18:10:25 +03:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
2019-06-20 18:21:55 +03:00
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
2019-06-05 13:43:07 +03:00
|
|
|
"strconv"
|
2019-06-20 18:21:55 +03:00
|
|
|
"strings"
|
2019-05-16 19:31:02 +03:00
|
|
|
|
2019-05-21 11:00:09 -07:00
|
|
|
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
2019-06-05 13:43:07 +03:00
|
|
|
"github.com/nirmata/kyverno/pkg/result"
|
2019-05-14 19:40:17 +03:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2019-05-14 18:10:25 +03:00
|
|
|
)
|
|
|
|
|
2019-05-15 19:25:49 +03:00
|
|
|
// Validate handles validating admission request
|
2019-06-05 17:43:59 -07:00
|
|
|
// Checks the target resources for rules defined in the policy
|
2019-06-05 13:43:07 +03:00
|
|
|
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) result.Result {
|
2019-05-14 18:10:25 +03:00
|
|
|
var resource interface{}
|
|
|
|
json.Unmarshal(rawResource, &resource)
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
policyResult := result.NewPolicyApplicationResult(policy.Name)
|
|
|
|
|
2019-05-20 14:48:38 +03:00
|
|
|
for _, rule := range policy.Spec.Rules {
|
|
|
|
if rule.Validation == nil {
|
2019-05-14 18:10:25 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
ruleApplicationResult := result.NewRuleApplicationResult(rule.Name)
|
|
|
|
|
2019-05-20 14:48:38 +03:00
|
|
|
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
|
2019-05-14 18:10:25 +03:00
|
|
|
if !ok {
|
2019-06-05 13:43:07 +03:00
|
|
|
ruleApplicationResult.AddMessagef("Rule %s is not applicable to resource\n", rule.Name)
|
|
|
|
policyResult = result.Append(policyResult, &ruleApplicationResult)
|
2019-05-14 18:10:25 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
validationResult := validateResourceWithPattern(resource, rule.Validation.Pattern)
|
|
|
|
if result.Success != validationResult.Reason {
|
|
|
|
ruleApplicationResult.AddMessagef(*rule.Validation.Message)
|
2019-06-10 17:06:31 +03:00
|
|
|
ruleApplicationResult.MergeWith(&validationResult)
|
2019-06-05 13:43:07 +03:00
|
|
|
} else {
|
|
|
|
ruleApplicationResult.AddMessagef("Success")
|
2019-05-14 18:10:25 +03:00
|
|
|
}
|
2019-06-05 13:43:07 +03:00
|
|
|
|
|
|
|
policyResult = result.Append(policyResult, &ruleApplicationResult)
|
2019-05-14 18:10:25 +03:00
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
return policyResult
|
2019-05-14 18:10:25 +03:00
|
|
|
}
|
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
// validateResourceWithPattern is a start of element-by-element validation process
|
|
|
|
// It assumes that validation is started from root, so "/" is passed
|
2019-06-05 13:43:07 +03:00
|
|
|
func validateResourceWithPattern(resource, pattern interface{}) result.RuleApplicationResult {
|
2019-06-20 18:21:55 +03:00
|
|
|
return validateResourceElement(resource, pattern, pattern, "/")
|
2019-06-05 13:43:07 +03:00
|
|
|
}
|
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
|
|
|
|
// and calls corresponding handler
|
|
|
|
// Pattern tree and resource tree can have different structure. In this case validation fails
|
2019-06-20 18:21:55 +03:00
|
|
|
func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) result.RuleApplicationResult {
|
2019-06-05 13:43:07 +03:00
|
|
|
res := result.NewRuleApplicationResult("")
|
|
|
|
// TODO: Move similar message templates to message package
|
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
switch typedPatternElement := patternElement.(type) {
|
|
|
|
// map
|
2019-06-05 13:43:07 +03:00
|
|
|
case map[string]interface{}:
|
2019-06-10 17:06:31 +03:00
|
|
|
typedResourceElement, ok := resourceElement.(map[string]interface{})
|
2019-06-05 13:43:07 +03:00
|
|
|
if !ok {
|
2019-06-10 17:06:31 +03:00
|
|
|
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
|
2019-06-05 13:43:07 +03:00
|
|
|
return res
|
|
|
|
}
|
2019-05-17 14:51:54 +03:00
|
|
|
|
2019-06-20 18:21:55 +03:00
|
|
|
return validateMap(typedResourceElement, typedPatternElement, originPattern, path)
|
2019-06-10 17:06:31 +03:00
|
|
|
// array
|
2019-06-05 13:43:07 +03:00
|
|
|
case []interface{}:
|
2019-06-10 17:06:31 +03:00
|
|
|
typedResourceElement, ok := resourceElement.([]interface{})
|
2019-06-05 13:43:07 +03:00
|
|
|
if !ok {
|
2019-06-10 17:06:31 +03:00
|
|
|
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
|
2019-06-05 13:43:07 +03:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2019-06-20 18:21:55 +03:00
|
|
|
return validateArray(typedResourceElement, typedPatternElement, originPattern, path)
|
2019-06-10 17:06:31 +03:00
|
|
|
// elementary values
|
2019-06-05 17:35:34 +03:00
|
|
|
case string, float64, int, int64, bool, nil:
|
2019-06-20 18:21:55 +03:00
|
|
|
/*Analyze pattern */
|
|
|
|
if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() == reflect.String {
|
|
|
|
if isStringIsReference(checkedPattern.String()) { //check for $ anchor
|
|
|
|
patternElement, res = actualizePattern(originPattern, checkedPattern.String(), path)
|
|
|
|
if result.Failed == res.Reason {
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-06-10 17:06:31 +03:00
|
|
|
if !ValidateValueWithPattern(resourceElement, patternElement) {
|
|
|
|
res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", resourceElement, patternElement, path)
|
2019-06-05 13:43:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
default:
|
2019-06-10 17:06:31 +03:00
|
|
|
res.FailWithMessagef("Pattern contains unknown type %T. Path: %s", patternElement, path)
|
2019-06-05 13:43:07 +03:00
|
|
|
return res
|
2019-05-15 19:25:49 +03:00
|
|
|
}
|
2019-06-05 13:43:07 +03:00
|
|
|
}
|
2019-05-15 19:25:49 +03:00
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
|
2019-06-12 12:21:52 +03:00
|
|
|
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
|
2019-06-20 18:21:55 +03:00
|
|
|
func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) result.RuleApplicationResult {
|
2019-06-05 13:43:07 +03:00
|
|
|
res := result.NewRuleApplicationResult("")
|
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
for key, patternElement := range patternMap {
|
|
|
|
key = removeAnchor(key)
|
2019-05-16 21:36:30 +03:00
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
// The '*' pattern means that key exists and has value
|
|
|
|
if patternElement == "*" && resourceMap[key] != nil {
|
2019-06-04 17:33:21 +03:00
|
|
|
continue
|
2019-06-10 17:06:31 +03:00
|
|
|
} else if patternElement == "*" && resourceMap[key] == nil {
|
2019-06-05 13:43:07 +03:00
|
|
|
res.FailWithMessagef("Field %s is not present", key)
|
2019-06-04 17:33:21 +03:00
|
|
|
} else {
|
2019-06-20 18:21:55 +03:00
|
|
|
elementResult := validateResourceElement(resourceMap[key], patternElement, origPattern, path+key+"/")
|
2019-06-10 17:10:05 +03:00
|
|
|
res.MergeWith(&elementResult)
|
2019-05-15 19:25:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
return res
|
2019-05-15 19:25:49 +03:00
|
|
|
}
|
|
|
|
|
2019-06-20 18:21:55 +03:00
|
|
|
func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) result.RuleApplicationResult {
|
2019-06-05 13:43:07 +03:00
|
|
|
res := result.NewRuleApplicationResult("")
|
2019-05-17 14:51:54 +03:00
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
if 0 == len(patternArray) {
|
|
|
|
return res
|
2019-05-15 19:25:49 +03:00
|
|
|
}
|
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
switch typedPatternElement := patternArray[0].(type) {
|
2019-05-15 19:25:49 +03:00
|
|
|
case map[string]interface{}:
|
2019-06-10 17:06:31 +03:00
|
|
|
// This is special case, because maps in arrays can have anchors that must be
|
|
|
|
// processed with the special way affecting the entire array
|
2019-06-20 18:21:55 +03:00
|
|
|
arrayResult := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path)
|
2019-06-10 17:06:31 +03:00
|
|
|
res.MergeWith(&arrayResult)
|
|
|
|
default:
|
|
|
|
// In all other cases - detect type and handle each array element with validateResourceElement
|
|
|
|
for i, patternElement := range patternArray {
|
2019-06-05 13:43:07 +03:00
|
|
|
currentPath := path + strconv.Itoa(i) + "/"
|
2019-06-20 18:21:55 +03:00
|
|
|
elementResult := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath)
|
2019-06-10 17:06:31 +03:00
|
|
|
res.MergeWith(&elementResult)
|
|
|
|
}
|
|
|
|
}
|
2019-05-16 17:37:05 +03:00
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
return res
|
|
|
|
}
|
2019-05-16 17:37:05 +03:00
|
|
|
|
2019-06-20 18:21:55 +03:00
|
|
|
func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, result.RuleApplicationResult) {
|
|
|
|
res := result.NewRuleApplicationResult("")
|
|
|
|
var foundValue interface{}
|
|
|
|
|
|
|
|
referencePattern = strings.Trim(referencePattern, "$()")
|
|
|
|
|
|
|
|
operator := getOperatorFromStringPattern(referencePattern)
|
|
|
|
referencePattern = referencePattern[len(operator):]
|
|
|
|
|
|
|
|
if len(referencePattern) == 0 {
|
|
|
|
res.FailWithMessagef("Expected path. Found empty reference")
|
|
|
|
return nil, res
|
|
|
|
}
|
|
|
|
|
|
|
|
actualPath := FormAbsolutePath(referencePattern, absolutePath)
|
|
|
|
|
|
|
|
valFromReference, res := getValueFromReference(origPattern, actualPath)
|
|
|
|
|
|
|
|
if result.Failed == res.Reason {
|
|
|
|
return nil, res
|
|
|
|
}
|
|
|
|
|
|
|
|
if operator == Equal { //if operator does not exist return raw value
|
|
|
|
return valFromReference, res
|
|
|
|
}
|
|
|
|
|
|
|
|
foundValue, res = valFromReferenceToString(valFromReference, string(operator))
|
|
|
|
|
|
|
|
return string(operator) + foundValue.(string), res
|
|
|
|
}
|
|
|
|
|
|
|
|
//Parse value to string
|
|
|
|
func valFromReferenceToString(value interface{}, operator string) (string, result.RuleApplicationResult) {
|
|
|
|
res := result.NewRuleApplicationResult("")
|
|
|
|
|
|
|
|
switch typed := value.(type) {
|
|
|
|
case string:
|
|
|
|
return typed, res
|
|
|
|
case int, int64:
|
|
|
|
return fmt.Sprintf("%d", value), res
|
|
|
|
case float64:
|
|
|
|
return fmt.Sprintf("%f", value), res
|
|
|
|
default:
|
|
|
|
res.FailWithMessagef("Incorrect expression. Operator %s does not match with value: %v", operator, value)
|
|
|
|
return "", res
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func FormAbsolutePath(referencePath, absolutePath string) string {
|
|
|
|
if filepath.IsAbs(referencePath) {
|
|
|
|
return referencePath
|
|
|
|
}
|
|
|
|
|
|
|
|
return filepath.Join(absolutePath, referencePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
//Prepares original pattern, path to value, and call traverse function
|
|
|
|
func getValueFromReference(origPattern interface{}, reference string) (interface{}, result.RuleApplicationResult) {
|
|
|
|
originalPatternMap := origPattern.(map[string]interface{})
|
|
|
|
reference = reference[1:len(reference)]
|
|
|
|
statements := strings.Split(reference, "/")
|
|
|
|
|
|
|
|
return getValueFromPattern(originalPatternMap, statements, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, result.RuleApplicationResult) {
|
|
|
|
res := result.NewRuleApplicationResult("")
|
|
|
|
|
|
|
|
for key, pattern := range patternMap {
|
|
|
|
rawKey := getRawKeyIfWrappedWithAttributes(key)
|
|
|
|
|
|
|
|
if rawKey == keys[len(keys)-1] && currentKeyIndex == len(keys)-1 {
|
|
|
|
return pattern, res
|
|
|
|
} else if rawKey != keys[currentKeyIndex] && currentKeyIndex != len(keys)-1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch typedPattern := pattern.(type) {
|
|
|
|
case []interface{}:
|
|
|
|
if keys[currentKeyIndex] == rawKey {
|
|
|
|
for i, value := range typedPattern {
|
|
|
|
resourceMap, ok := value.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
res.FailWithMessagef("Pattern and resource have different structures. Expected %T, found %T", pattern, value)
|
|
|
|
return nil, res
|
|
|
|
}
|
|
|
|
if keys[currentKeyIndex+1] == strconv.Itoa(i) {
|
|
|
|
return getValueFromPattern(resourceMap, keys, currentKeyIndex+2)
|
|
|
|
}
|
|
|
|
res.FailWithMessagef("Reference to non-existent place in the document")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res.FailWithMessagef("Reference to non-existent place in the document")
|
|
|
|
case map[string]interface{}:
|
|
|
|
if keys[currentKeyIndex] == rawKey {
|
|
|
|
return getValueFromPattern(typedPattern, keys, currentKeyIndex+1)
|
|
|
|
}
|
|
|
|
res.FailWithMessagef("Reference to non-existent place in the document")
|
|
|
|
case string, float64, int, int64, bool, nil:
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
path := ""
|
|
|
|
|
|
|
|
/*for i := len(keys) - 1; i >= 0; i-- {
|
|
|
|
path = keys[i] + path + "/"
|
|
|
|
}*/
|
|
|
|
for _, elem := range keys {
|
|
|
|
path = "/" + elem + path
|
|
|
|
}
|
|
|
|
res.FailWithMessagef("No value found for specified reference: %s", path)
|
|
|
|
return nil, res
|
|
|
|
}
|
|
|
|
|
2019-06-10 17:06:31 +03:00
|
|
|
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
|
|
|
|
// and then validates each map due to the pattern
|
2019-06-20 18:21:55 +03:00
|
|
|
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) result.RuleApplicationResult {
|
2019-06-13 17:20:00 +03:00
|
|
|
anchor, pattern := getAnchorFromMap(patternMap)
|
2019-06-10 17:06:31 +03:00
|
|
|
|
2019-06-13 17:20:00 +03:00
|
|
|
handler := CreateAnchorHandler(anchor, pattern, path)
|
2019-06-20 18:21:55 +03:00
|
|
|
return handler.Handle(resourceMapArray, patternMap, originPattern)
|
2019-05-15 19:25:49 +03:00
|
|
|
}
|