1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-17 17:56:33 +00:00

support existance on list type

This commit is contained in:
shivkumar dudhani 2019-09-25 21:01:45 -07:00
parent 974fff169a
commit 087efffd96
3 changed files with 418 additions and 67 deletions

View file

@ -7,8 +7,22 @@ import (
"github.com/golang/glog"
)
func getAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
if isConditionAnchor(key) || isExistanceAnchor(key) {
anchors[key] = value
continue
}
resources[key] = value
}
return anchors, resources
}
type ValidationHandler interface {
Handle(resourceMap map[string]interface{}, originPattenr interface{}) (string, bool, error)
Handle(resourceMap map[string]interface{}, originPattenr interface{}) (string, error)
}
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
@ -48,21 +62,19 @@ type DefaultHandler struct {
path string
}
func (dh DefaultHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, bool, error) {
// skip is used by existance anchor to not process further if condition is not satisfied
skip := false
func (dh DefaultHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
currentPath := dh.path + dh.element + "/"
if dh.pattern == "*" && resourceMap[dh.element] != nil {
return "", skip, nil
return "", nil
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
return dh.path, skip, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
} else {
path, err := validateResourceElement(resourceMap[dh.element], dh.pattern, originPattern, currentPath)
if err != nil {
return path, skip, err
return path, err
}
}
return "", skip, nil
return "", nil
}
func NewConditionAnchorHandler(anchor string, pattern interface{}, path string) ValidationHandler {
@ -79,33 +91,50 @@ type ConditionAnchorHandler struct {
path string
}
func (ch ConditionAnchorHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, bool, error) {
// skip is used by existance anchor to not process further if condition is not satisfied
skip := false
var value interface{}
var currentPath string
var ok bool
// check for anchor condition
anchorSatisfied := func() bool {
anchorKey := removeAnchor(ch.anchor)
currentPath = ch.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok = resourceMap[anchorKey]; ok {
// if the key exists then we process its values
return true
// return ValidateValueWithPattern(value, ch.pattern)
func (ch ConditionAnchorHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
anchorKey := removeAnchor(ch.anchor)
currentPath := ch.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
if err != nil {
return returnPath, err
}
return false
}()
return "", nil
if !anchorSatisfied {
return "", skip, nil
}
return "", nil
path, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
if err != nil {
return path, skip, err
}
// return false
// var value interface{}
// var currentPath string
// var ok bool
// // check for anchor condition
// anchorSatisfied := func() bool {
// anchorKey := removeAnchor(ch.anchor)
// currentPath = ch.path + anchorKey + "/"
// // check if anchor is present in resource
// if value, ok = resourceMap[anchorKey]; ok {
// // validate the values of the pattern
// _, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
// if err == nil {
// return true
// }
// // return ValidateValueWithPattern(value, ch.pattern)
// }
// return false
// }()
// if !anchorSatisfied {
// return "", nil
// }
// path, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
// if err != nil {
// return path, err
// }
// evauluate the anchor and resource values
// for key, element := range resourceMap {
// currentPath := ch.path + key + "/"
@ -118,7 +147,7 @@ func (ch ConditionAnchorHandler) Handle(resourceMap map[string]interface{}, orig
// return path, err
// }
// }
return "", skip, nil
return "", nil
}
func NewExistanceHandler(anchor string, pattern interface{}, path string) ValidationHandler {
@ -135,36 +164,36 @@ type ExistanceHandler struct {
path string
}
func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, bool, error) {
func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
// skip is used by existance anchor to not process further if condition is not satisfied
var value interface{}
var currentPath string
var ok bool
// anchoredEntries := 0
// check for anchor condition
anchorSatisfied := func() bool {
anchorKey := removeAnchor(eh.anchor)
currentPath = eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok = resourceMap[anchorKey]; ok {
// if the key exists then validate
// not handled for arrays
// maps we only check if key exists
_, err := validateResourceElement(value, eh.pattern, originPattern, currentPath)
if err == nil {
// if the anchor value is the satisfied then we evaluate the next
return true
anchorKey := removeAnchor(eh.anchor)
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// Existance anchor can only exist on resource value type of list
switch typedResource := value.(type) {
case []interface{}:
typedPattern, ok := eh.pattern.([]interface{})
if !ok {
return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of lis to compare against resource", eh.pattern)
}
// return ValidateValueWithPattern(value, eh.pattern)
// get the first item in the pattern array
patternMap := typedPattern[0]
typedPatternMap, ok := patternMap.(map[string]interface{})
if !ok {
return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
}
return validateExistenceListResource(typedResource, typedPatternMap, originPattern, currentPath)
default:
glog.Error("Invalid type: Existance ^ () anchor can be used only on list/array type resource")
return currentPath, fmt.Errorf("Invalid resource type %T: Existance ^ () anchor can be used only on list/array type resource", value)
}
return false
}()
if !anchorSatisfied {
// if the existance anchor is not satisfied then we dont process that node further
// so we skip processing further
return "", true, nil
_, err := validateResourceElement(value, eh.pattern, originPattern, currentPath)
if err == nil {
// if the anchor value is the satisfied then we evaluate the next
return "", nil
}
// return ValidateValueWithPattern(value, eh.pattern)
}
// anchoredEntries++
@ -175,7 +204,7 @@ func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPatt
// if anchoredEntries == 0 {
// return eh.path, fmt.Errorf("Existance anchor %s used, but no suitable entries were found", eh.anchor)
// }
return "", false, nil
return "", nil
// anchoredEntries := 0
// for key, element := range resourceMap {
@ -197,6 +226,22 @@ func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPatt
// return "", nil
}
func validateExistenceListResource(resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
// the idea is atleast on the elements in the array should satisfy the pattern
// if non satisfy then throw an error
for i, resourceElement := range resourceList {
currentPath := path + strconv.Itoa(i) + "/"
_, err := validateResourceElement(resourceElement, patternMap, originPattern, currentPath)
if err == nil {
// condition is satisfied, dont check further
glog.V(4).Infof("Existence check satisfied at path %s, for pattern %v", currentPath, patternMap)
return "", nil
}
}
// none of the existance checks worked, so thats a failure sceanario
return path, fmt.Errorf("Existence anchor validation failed at path %s", path)
}
// ValidationAnchorHandler is an interface that represents
// a family of anchor handlers for array of maps
// resourcePart must be an array of dictionaries

View file

@ -186,23 +186,30 @@ func validateResourceElement(resourceElement, patternElement, originPattern inte
func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) {
// check if there is anchor in pattern
// anchor, pattern := getAnchorFromMap(patternMap)
// Phase 1 : Evaluate all the anchors
// Phase 2 : Evaluate non-anchors
anchors, resources := getAnchorsResourcesFromMap(patternMap)
for key, patternElement := range patternMap {
// Evaluate anchors
for key, patternElement := range anchors {
// get handler for each pattern in the pattern
// - Anchor
// - Existance
// - No Anchor(Default)
handler := CreateElementHandler(key, patternElement, path)
handlerPath, skip, err := handler.Handle(resourceMap, origPattern)
handlerPath, err := handler.Handle(resourceMap, origPattern)
if err != nil {
return handlerPath, err
}
if skip {
// for existance anchor, if not present, then dont process the node in the tree further
return "", nil
}
// Evaluate resources
for key, resourceElement := range resources {
// get handler for resources in the pattern
handler := CreateElementHandler(key, resourceElement, path)
handlerPath, err := handler.Handle(resourceMap, origPattern)
if err != nil {
return handlerPath, err
}
}
return "", nil
}

View file

@ -2469,3 +2469,302 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) {
}
assert.Assert(t, !er.IsSuccesful())
}
func TestValidate_AnchorList_pass(t *testing.T) {
// anchor not present in resource
rawPolicy := []byte(`
{
"apiVersion": "kyverno.io/v1alpha1",
"kind": "ClusterPolicy",
"metadata": {
"name": "policy-secaas-k8s"
},
"spec": {
"rules": [
{
"name": "pod image rule",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"pattern": {
"spec": {
"(containers)": [
{
"name": "nginx"
}
]
}
}
}
}
]
}
}
`)
rawResource := []byte(`
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "myapp-pod",
"labels": {
"app": "v1"
}
},
"spec": {
"containers": [
{
"name": "nginx"
},
{
"name": "nginx"
}
]
}
}
`)
var policy kyverno.ClusterPolicy
json.Unmarshal(rawPolicy, &policy)
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
// msgs := []string{"Validation rule 'pod rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user"}
for _, r := range er.PolicyResponse.Rules {
t.Error(r.Message)
// assert.Equal(t, r.Message, msgs[index])
}
assert.Assert(t, !er.IsSuccesful())
}
func TestValidate_AnchorList_fail(t *testing.T) {
rawPolicy := []byte(`
{
"apiVersion": "kyverno.io/v1alpha1",
"kind": "ClusterPolicy",
"metadata": {
"name": "policy-secaas-k8s"
},
"spec": {
"rules": [
{
"name": "pod image rule",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"pattern": {
"spec": {
"(containers)": [
{
"name": "nginx"
}
]
}
}
}
}
]
}
}
`)
rawResource := []byte(`
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "myapp-pod",
"labels": {
"app": "v1"
}
},
"spec": {
"containers": [
{
"name": "nginx"
},
{
"name": "busy"
}
]
}
}
`)
var policy kyverno.ClusterPolicy
json.Unmarshal(rawPolicy, &policy)
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
// msgs := []string{"Validation rule 'pod rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user"}
for _, r := range er.PolicyResponse.Rules {
t.Error(r.Message)
// assert.Equal(t, r.Message, msgs[index])
}
assert.Assert(t, !er.IsSuccesful())
}
func TestValidate_existenceAnchor_fail(t *testing.T) {
// anchor not present in resource
rawPolicy := []byte(`
{
"apiVersion": "kyverno.io/v1alpha1",
"kind": "ClusterPolicy",
"metadata": {
"name": "policy-secaas-k8s"
},
"spec": {
"rules": [
{
"name": "pod image rule",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"pattern": {
"spec": {
"^(containers)": [
{
"name": "nginx"
}
]
}
}
}
}
]
}
}
`)
rawResource := []byte(`
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "myapp-pod",
"labels": {
"app": "v1"
}
},
"spec": {
"containers": [
{
"name": "busy1"
},
{
"name": "busy"
}
]
}
}
`)
var policy kyverno.ClusterPolicy
json.Unmarshal(rawPolicy, &policy)
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
// msgs := []string{"Validation rule 'pod rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user"}
for _, r := range er.PolicyResponse.Rules {
t.Error(r.Message)
// assert.Equal(t, r.Message, msgs[index])
}
assert.Assert(t, !er.IsSuccesful())
}
func TestValidate_existenceAnchor_pass(t *testing.T) {
// anchor not present in resource
rawPolicy := []byte(`
{
"apiVersion": "kyverno.io/v1alpha1",
"kind": "ClusterPolicy",
"metadata": {
"name": "policy-secaas-k8s"
},
"spec": {
"rules": [
{
"name": "pod image rule",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"pattern": {
"spec": {
"^(containers)": [
{
"name": "nginx"
}
]
}
}
}
}
]
}
}
`)
rawResource := []byte(`
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "myapp-pod",
"labels": {
"app": "v1"
}
},
"spec": {
"containers": [
{
"name": "nginx"
},
{
"name": "busy"
}
]
}
}
`)
var policy kyverno.ClusterPolicy
json.Unmarshal(rawPolicy, &policy)
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
// msgs := []string{"Validation rule 'pod rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user"}
for _, r := range er.PolicyResponse.Rules {
t.Error(r.Message)
// assert.Equal(t, r.Message, msgs[index])
}
assert.Assert(t, !er.IsSuccesful())
}