2019-05-13 18:17:28 -07:00
|
|
|
package engine
|
|
|
|
|
2019-05-14 18:10:25 +03:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
2019-06-05 13:43:07 +03:00
|
|
|
"strconv"
|
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
|
|
|
|
// Checks the target resourse 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.MergeWith(&validationResult)
|
|
|
|
ruleApplicationResult.AddMessagef(*rule.Validation.Message)
|
|
|
|
} 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-05 13:43:07 +03:00
|
|
|
func validateResourceWithPattern(resource, pattern interface{}) result.RuleApplicationResult {
|
|
|
|
return validateResourceElement(resource, pattern, "/")
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateResourceElement(value, pattern interface{}, path string) result.RuleApplicationResult {
|
|
|
|
res := result.NewRuleApplicationResult("")
|
|
|
|
// TODO: Move similar message templates to message package
|
|
|
|
|
|
|
|
switch typedPattern := pattern.(type) {
|
|
|
|
case map[string]interface{}:
|
|
|
|
typedValue, ok := value.(map[string]interface{})
|
|
|
|
if !ok {
|
2019-06-05 15:02:58 +03:00
|
|
|
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, pattern, value)
|
2019-06-05 13:43:07 +03:00
|
|
|
return res
|
|
|
|
}
|
2019-05-17 14:51:54 +03:00
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
return validateMap(typedValue, typedPattern, path)
|
|
|
|
case []interface{}:
|
|
|
|
typedValue, ok := value.([]interface{})
|
|
|
|
if !ok {
|
2019-06-05 15:02:58 +03:00
|
|
|
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, pattern, value)
|
2019-06-05 13:43:07 +03:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
return validateArray(typedValue, typedPattern, path)
|
2019-06-05 17:35:34 +03:00
|
|
|
case string, float64, int, int64, bool, nil:
|
2019-06-05 13:43:07 +03:00
|
|
|
if !ValidateValueWithPattern(value, pattern) {
|
|
|
|
res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", value, pattern, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
default:
|
|
|
|
res.FailWithMessagef("Pattern contains unknown type %T. Path: %s", pattern, path)
|
|
|
|
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-05 13:43:07 +03:00
|
|
|
func validateMap(valueMap, patternMap map[string]interface{}, path string) result.RuleApplicationResult {
|
|
|
|
res := result.NewRuleApplicationResult("")
|
|
|
|
|
|
|
|
for key, pattern := range patternMap {
|
2019-05-16 21:36:30 +03:00
|
|
|
if wrappedWithParentheses(key) {
|
|
|
|
key = key[1 : len(key)-1]
|
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
if pattern == "*" && valueMap[key] != nil {
|
2019-06-04 17:33:21 +03:00
|
|
|
continue
|
2019-06-05 13:43:07 +03:00
|
|
|
} else if pattern == "*" && valueMap[key] == nil {
|
|
|
|
res.FailWithMessagef("Field %s is not present", key)
|
2019-06-04 17:33:21 +03:00
|
|
|
} else {
|
2019-06-05 13:43:07 +03:00
|
|
|
elementResult := validateResourceElement(valueMap[key], pattern, path+key+"/")
|
|
|
|
if result.Failed == elementResult.Reason {
|
|
|
|
res.Reason = elementResult.Reason
|
|
|
|
res.Messages = append(res.Messages, elementResult.Messages...)
|
2019-06-04 17:33:21 +03:00
|
|
|
}
|
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-05 13:43:07 +03:00
|
|
|
return res
|
2019-05-15 19:25:49 +03:00
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
func validateArray(resourceArray, patternArray []interface{}, path string) result.RuleApplicationResult {
|
|
|
|
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-05-16 17:37:05 +03:00
|
|
|
switch pattern := patternArray[0].(type) {
|
2019-05-15 19:25:49 +03:00
|
|
|
case map[string]interface{}:
|
2019-05-21 18:27:56 +03:00
|
|
|
anchors := GetAnchorsFromMap(pattern)
|
2019-06-05 13:43:07 +03:00
|
|
|
for i, value := range resourceArray {
|
|
|
|
currentPath := path + strconv.Itoa(i) + "/"
|
2019-05-16 17:37:05 +03:00
|
|
|
resource, ok := value.(map[string]interface{})
|
|
|
|
if !ok {
|
2019-06-05 15:02:58 +03:00
|
|
|
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, pattern, value)
|
2019-06-05 13:43:07 +03:00
|
|
|
return res
|
2019-05-16 17:37:05 +03:00
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
if skipArrayObject(resource, anchors) {
|
2019-05-16 17:37:05 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
mapValidationResult := validateMap(resource, pattern, currentPath)
|
|
|
|
if result.Failed == mapValidationResult.Reason {
|
|
|
|
res.Reason = mapValidationResult.Reason
|
|
|
|
res.Messages = append(res.Messages, mapValidationResult.Messages...)
|
2019-05-16 21:36:30 +03:00
|
|
|
}
|
2019-05-16 17:37:05 +03:00
|
|
|
}
|
2019-06-05 17:35:34 +03:00
|
|
|
case string, float64, int, int64, bool, nil:
|
2019-05-16 17:37:05 +03:00
|
|
|
for _, value := range resourceArray {
|
2019-06-05 13:43:07 +03:00
|
|
|
if !ValidateValueWithPattern(value, pattern) {
|
|
|
|
res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", value, pattern, path)
|
2019-05-16 17:37:05 +03:00
|
|
|
}
|
|
|
|
}
|
2019-05-14 18:10:25 +03:00
|
|
|
default:
|
2019-06-05 13:43:07 +03:00
|
|
|
res.FailWithMessagef("Array element pattern of unknown type %T. Path: %s", pattern, path)
|
2019-05-16 19:31:02 +03:00
|
|
|
}
|
|
|
|
|
2019-06-05 13:43:07 +03:00
|
|
|
return res
|
2019-05-15 19:25:49 +03:00
|
|
|
}
|