1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 18:15:48 +00:00

return failure path for mutate condition check

This commit is contained in:
Shuting Zhao 2019-11-01 11:14:58 -07:00
parent ef8bf695b1
commit 86c00a8f30
4 changed files with 163 additions and 158 deletions

View file

@ -31,7 +31,7 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp
patches, err := processOverlayPatches(resource.UnstructuredContent(), rule.Mutation.Overlay)
// resource does not satisfy the overlay pattern, we dont apply this rule
if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
glog.V(4).Infof("Resource %s/%s/%s does not meet the conditions in the rule %s with overlay pattern %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Name, rule.Mutation.Overlay)
glog.Errorf("Resource %s/%s/%s does not meet the conditions in the rule %s with overlay pattern %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Name, rule.Mutation.Overlay)
//TODO: send zero response and not consider this as applied?
return RuleResponse{}, resource
}
@ -74,10 +74,12 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp
// apply the patches to the resource
return response, patchedResource
}
func processOverlayPatches(resource, overlay interface{}) ([][]byte, error) {
if !meetConditions(resource, overlay) {
return nil, errors.New("Conditions are not met")
if path, err := meetConditions(resource, overlay); err != nil {
glog.V(4).Infof("Mutate rule: failed to validate condition at %s, err: %v", path, err)
return nil, fmt.Errorf("Conditions are not met at %s, %v", path, err)
}
return mutateResourceWithOverlay(resource, overlay)

View file

@ -1,16 +1,23 @@
package engine
import (
"fmt"
"reflect"
"strconv"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/anchor"
)
func meetConditions(resource, overlay interface{}) bool {
func meetConditions(resource, overlay interface{}) (string, error) {
return checkConditions(resource, overlay, "/")
}
// resource and overlay should be the same type
func checkConditions(resource, overlay interface{}, path string) (string, error) {
// overlay has no anchor, return true
if !hasNestedAnchors(overlay) {
return true
return "", nil
}
// resource item exists but has different type
@ -18,92 +25,57 @@ func meetConditions(resource, overlay interface{}) bool {
// conditon never be true in this case
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
if hasNestedAnchors(overlay) {
glog.Errorf("Found anchor on different types of element: overlay %T, %v, resource %T, %v\nSkip processing overlay.", overlay, overlay, resource, resource)
return false
glog.V(4).Infof("Found anchor on different types of element at path %s: overlay %T, resource %T", path, overlay, resource)
return path, fmt.Errorf("Found anchor on different types of element at path %s: overlay %T %v, resource %T %v", path, overlay, overlay, resource, resource)
}
return true
return "", nil
}
return checkConditions(resource, overlay)
}
// resource and overlay should be the same type
func checkConditions(resource, overlay interface{}) bool {
switch typedOverlay := overlay.(type) {
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
return checkConditionOnMap(typedResource, typedOverlay)
return checkConditionOnMap(typedResource, typedOverlay, path)
case []interface{}:
typedResource := resource.([]interface{})
return checkConditionOnArray(typedResource, typedOverlay)
return checkConditionOnArray(typedResource, typedOverlay, path)
default:
// anchor on non map/array is invalid:
// - anchor defined on values
return true
return "", nil
}
}
// compareOverlay compare values in anchormap and resourcemap
// i.e. check if B1 == B2
// overlay - (A): B1
// resource - A: B2
func compareOverlay(resource, overlay interface{}) bool {
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
glog.Errorf("Found anchor on different types of element: overlay %T, resource %T\nSkip processing overlay.", overlay, resource)
return false
}
switch typedOverlay := overlay.(type) {
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
for key, overlayVal := range typedOverlay {
noAnchorKey := removeAnchor(key)
resourceVal, ok := typedResource[noAnchorKey]
if !ok {
return false
}
if !compareOverlay(resourceVal, overlayVal) {
return false
}
}
case []interface{}:
typedResource := resource.([]interface{})
for _, overlayElement := range typedOverlay {
for _, resourceElement := range typedResource {
if !compareOverlay(resourceElement, overlayElement) {
return false
}
}
}
case string, float64, int, int64, bool, nil:
if !ValidateValueWithPattern(resource, overlay) {
glog.Errorf("Mutate rule failed validating value %v with overlay %v", resource, overlay)
return false
}
default:
glog.Errorf("Mutate overlay has unknown type %T, value %v", overlay, overlay)
return false
}
return true
}
func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}) bool {
func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}, path string) (string, error) {
anchors, overlayWithoutAnchor := getAnchorAndElementsFromMap(overlayMap)
if !validateConditionAnchorMap(resourceMap, anchors) {
return false
if newPath, err := validateConditionAnchorMap(resourceMap, anchors, path); err != nil {
return newPath, err
}
if !validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor) {
return false
if newPath, err := validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor, path); err != nil {
return newPath, err
}
// empty overlayMap
return true
return "", nil
}
func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}) bool {
func checkConditionOnArray(resource, overlay []interface{}, path string) (string, error) {
if 0 == len(overlay) {
glog.Infof("Mutate overlay pattern is empty, path %s", path)
return "", nil
}
if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) {
glog.V(4).Infof("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
return path, fmt.Errorf("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
}
return checkConditionsOnArrayOfSameTypes(resource, overlay, path)
}
func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}, path string) (string, error) {
for key, overlayValue := range anchors {
// skip if key does not have condition anchor
if !anchor.IsConditionAnchor(key) {
@ -112,21 +84,68 @@ func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}) boo
// validate condition anchor map
noAnchorKey := removeAnchor(key)
curPath := path + noAnchorKey + "/"
if resourceValue, ok := resourceMap[noAnchorKey]; ok {
if !compareOverlay(resourceValue, overlayValue) {
return false
if newPath, err := compareOverlay(resourceValue, overlayValue, curPath); err != nil {
return newPath, err
}
} else {
// noAnchorKey doesn't exist in resource
return false
return curPath, fmt.Errorf("resource field %s is not present", noAnchorKey)
}
}
return true
return "", nil
}
func validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor map[string]interface{}) bool {
// compareOverlay compare values in anchormap and resourcemap
// i.e. check if B1 == B2
// overlay - (A): B1
// resource - A: B2
func compareOverlay(resource, overlay interface{}, path string) (string, error) {
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
glog.Errorf("Found anchor on different types of element: overlay %T, resource %T\nSkip processing overlay.", overlay, resource)
return path, fmt.Errorf("")
}
switch typedOverlay := overlay.(type) {
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
for key, overlayVal := range typedOverlay {
noAnchorKey := removeAnchor(key)
curPath := path + noAnchorKey + "/"
resourceVal, ok := typedResource[noAnchorKey]
if !ok {
return curPath, fmt.Errorf("Field %s is not present", noAnchorKey)
}
if newPath, err := compareOverlay(resourceVal, overlayVal, curPath); err != nil {
return newPath, err
}
}
case []interface{}:
typedResource := resource.([]interface{})
for _, overlayElement := range typedOverlay {
for _, resourceElement := range typedResource {
if newPath, err := compareOverlay(resourceElement, overlayElement, path); err != nil {
return newPath, err
}
}
}
case string, float64, int, int64, bool, nil:
if !ValidateValueWithPattern(resource, overlay) {
glog.V(4).Infof("Mutate rule: failed validating value %v with overlay %v", resource, overlay)
return path, fmt.Errorf("failed validating value %v with overlay %v", resource, overlay)
}
default:
return path, fmt.Errorf("overlay has unknown type %T, value %v", overlay, overlay)
}
return "", nil
}
func validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor map[string]interface{}, path string) (string, error) {
// validate resource map (anchors could exist in resource)
for key, overlayValue := range overlayWithoutAnchor {
curPath := path + key + "/"
resourceValue, ok := resourceMap[key]
if !ok {
// policy: "(image)": "*:latest",
@ -135,48 +154,37 @@ func validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor map[string]in
// the above case should be allowed
continue
}
if !meetConditions(resourceValue, overlayValue) {
return false
if newPath, err := checkConditions(resourceValue, overlayValue, curPath); err != nil {
return newPath, err
}
}
return true
return "", nil
}
func checkConditionOnArray(resource, overlay []interface{}) bool {
if 0 == len(resource) {
return false
}
if 0 == len(overlay) {
return true
}
if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) {
glog.Warningf("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
return false
}
return checkConditionsOnArrayOfSameTypes(resource, overlay)
}
func checkConditionsOnArrayOfSameTypes(resource, overlay []interface{}) bool {
func checkConditionsOnArrayOfSameTypes(resource, overlay []interface{}, path string) (string, error) {
switch overlay[0].(type) {
case map[string]interface{}:
return checkConditionsOnArrayOfMaps(resource, overlay)
return checkConditionsOnArrayOfMaps(resource, overlay, path)
default:
// TODO: array of array?
glog.Warningf("Anchors not supported in overlay of array type %T\n", overlay[0])
return false
}
}
func checkConditionsOnArrayOfMaps(resource, overlay []interface{}) bool {
for _, overlayElement := range overlay {
for _, resourceMap := range resource {
if !checkConditionOnMap(resourceMap.(map[string]interface{}), overlayElement.(map[string]interface{})) {
return false
for i, overlayElement := range overlay {
curPath := path + strconv.Itoa(i) + "/"
path, err := checkConditions(resource[i], overlayElement, curPath)
if err != nil {
return path, err
}
}
}
return true
return "", nil
}
func checkConditionsOnArrayOfMaps(resource, overlay []interface{}, path string) (string, error) {
for i, overlayElement := range overlay {
for _, resourceMap := range resource {
curPath := path + strconv.Itoa(i) + "/"
if newPath, err := checkConditionOnMap(resourceMap.(map[string]interface{}), overlayElement.(map[string]interface{}), curPath); err != nil {
return newPath, err
}
}
}
return "", nil
}

View file

@ -2,6 +2,7 @@ package engine
import (
"encoding/json"
"strings"
"testing"
"gotest.tools/assert"
@ -26,8 +27,8 @@ func TestMeetConditions_NoAnchor(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(nil, overlay)
assert.Assert(t, res)
_, err := meetConditions(nil, overlay)
assert.Assert(t, err == nil)
}
func TestMeetConditions_conditionalAnchorOnMap(t *testing.T) {
@ -79,8 +80,8 @@ func TestMeetConditions_conditionalAnchorOnMap(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, !res)
_, err := meetConditions(resource, overlay)
assert.Assert(t, err != nil)
overlayRaw = []byte(`
{
@ -90,7 +91,7 @@ func TestMeetConditions_conditionalAnchorOnMap(t *testing.T) {
{
"name":"secure-connection",
"port":443,
"protocol":"TCP"
"(protocol)":"TCP"
}
]
}
@ -99,28 +100,24 @@ func TestMeetConditions_conditionalAnchorOnMap(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.NilError(t, err)
}
func TestMeetConditions_DifferentTypes(t *testing.T) {
resourceRaw := []byte(`
{
"apiVersion":"v1",
"kind":"Endpoints",
"metadata":{
"name":"test-endpoint",
{
"apiVersion": "v1",
"kind": "Endpoints",
"metadata": {
"name": "test-endpoint"
},
"subsets":[
{
"addresses":[
{
"ip":"192.168.10.171"
}
],
"subsets": {
"addresses": {
"ip": "192.168.10.171"
}
]
}`)
}
}`)
overlayRaw := []byte(`
{
@ -142,8 +139,8 @@ func TestMeetConditions_DifferentTypes(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
// anchor exist
res := meetConditions(resource, overlay)
assert.Assert(t, !res)
_, err := meetConditions(resource, overlay)
assert.Assert(t, strings.Contains(err.Error(), "Found anchor on different types of element at path /subsets/"))
}
func TestMeetConditions_anchosInSameObject(t *testing.T) {
@ -195,9 +192,8 @@ func TestMeetConditions_anchosInSameObject(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
// no anchor
res := meetConditions(resource, overlay)
assert.Assert(t, !res)
_, err := meetConditions(resource, overlay)
assert.Error(t, err, "failed validating value 443 with overlay 444")
}
func TestMeetConditions_anchorOnPeer(t *testing.T) {
@ -254,8 +250,8 @@ func TestMeetConditions_anchorOnPeer(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.NilError(t, err)
}
func TestMeetConditions_anchorsOnMetaAndSpec(t *testing.T) {
@ -331,8 +327,8 @@ func TestMeetConditions_anchorsOnMetaAndSpec(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.NilError(t, err)
}
var resourceRawAnchorOnPeers = []byte(`{
@ -412,8 +408,8 @@ func TestMeetConditions_anchorsOnPeer_single(t *testing.T) {
json.Unmarshal(resourceRawAnchorOnPeers, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.NilError(t, err)
}
func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
@ -425,7 +421,7 @@ func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
{
"(image)": "*/nginx-unprivileged",
"securityContext": {
"(runAsNonRoot)": true,
"(runAsNonRoot)": false,
"allowPrivilegeEscalation": false
},
"env": [
@ -446,8 +442,8 @@ func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
json.Unmarshal(resourceRawAnchorOnPeers, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.Error(t, err, "failed validating value true with overlay false")
overlayRaw = []byte(`{
"spec": {
@ -475,8 +471,8 @@ func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.NilError(t, err)
overlayRaw = []byte(`{
"spec": {
@ -504,8 +500,8 @@ func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.NilError(t, err)
}
func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
@ -538,8 +534,8 @@ func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
json.Unmarshal(resourceRawAnchorOnPeers, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.NilError(t, err)
overlayRaw = []byte(`{
"spec": {
@ -567,8 +563,8 @@ func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.NilError(t, err)
overlayRaw = []byte(`{
"spec": {
@ -581,10 +577,10 @@ func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
"runAsNonRoot": true,
"(allowPrivilegeEscalation)": false
},
"env": [
"(env)": [
{
"(name)": "ENV_KEY",
"(value)": "ENV_VALUE"
"name": "ENV_KEY",
"value": "ENV_VALUE1"
}
]
}
@ -596,7 +592,6 @@ func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.Error(t, err, "failed validating value ENV_VALUE with overlay ENV_VALUE1")
}

View file

@ -494,7 +494,7 @@ func TestProcessOverlayPatches_ImagePullPolicy(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
patches, err = processOverlayPatches(resource, overlay)
assert.Error(t, err, "Conditions are not met")
assert.Error(t, err, "Conditions are not met at /spec/template/metadata/labels/app/, failed validating value nginx with overlay nginx1")
assert.Assert(t, len(patches) == 0)
}
@ -807,7 +807,7 @@ func TestProcessOverlayPatches_anchorOnPeer(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
patches, err = processOverlayPatches(resource, overlay)
assert.Error(t, err, "Conditions are not met")
assert.Error(t, err, "Conditions are not met at /subsets/0/ports/0/port/, failed validating value 443 with overlay 444")
assert.Assert(t, len(patches) == 0)
}