From 6d49a728a1ccd6d8176c4de952f648c06d4e6095 Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Tue, 23 Jul 2019 17:54:31 -0700 Subject: [PATCH 1/8] - update install_debug.yaml - add debug log --- definitions/install_debug.yaml | 7 +------ pkg/webhooks/mutation.go | 2 +- pkg/webhooks/registration.go | 2 ++ pkg/webhooks/server.go | 3 ++- pkg/webhooks/validation.go | 3 ++- scripts/compile-image.sh | 2 +- scripts/generate-self-signed-cert-and-k8secrets-debug.sh | 1 + 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/definitions/install_debug.yaml b/definitions/install_debug.yaml index 732b3cd5f7..857ab4ad52 100644 --- a/definitions/install_debug.yaml +++ b/definitions/install_debug.yaml @@ -124,9 +124,4 @@ spec: name: type: string data: - AnyValue: {} ---- -kind: Namespace -apiVersion: v1 -metadata: - name: "kyverno" \ No newline at end of file + AnyValue: {} \ No newline at end of file diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index 7ed212bd00..6e839e9145 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -13,7 +13,7 @@ import ( // HandleMutation handles mutating webhook admission request func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { - glog.V(3).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", + glog.V(4).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation) policies, err := ws.policyLister.List(labels.NewSelector()) diff --git a/pkg/webhooks/registration.go b/pkg/webhooks/registration.go index b6e63124ad..adb35abbe5 100644 --- a/pkg/webhooks/registration.go +++ b/pkg/webhooks/registration.go @@ -33,6 +33,8 @@ func NewWebhookRegistrationClient(clientConfig *rest.Config, client *client.Clie return nil, err } + glog.V(3).Infof("Registering webhook client using serverIP %s\n", serverIP) + return &WebhookRegistrationClient{ registrationClient: registrationClient, client: client, diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 515b1fb4d7..2a4bd987c2 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -137,9 +137,10 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { // RunAsync TLS server in separate thread and returns control immediately func (ws *WebhookServer) RunAsync() { go func(ws *WebhookServer) { + glog.V(3).Infof("serving on %s\n", ws.server.Addr) err := ws.server.ListenAndServeTLS("", "") if err != nil { - glog.Fatal(err) + glog.Fatalf("error serving TLS: %v\n", err) } }(ws) glog.Info("Started Webhook Server") diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go index 7a335402ff..66b418bd34 100644 --- a/pkg/webhooks/validation.go +++ b/pkg/webhooks/validation.go @@ -13,7 +13,8 @@ import ( // HandleValidation handles validating webhook admission request // If there are no errors in validating rule we apply generation rules func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { - glog.V(3).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", + + glog.V(4).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation) policyInfos := []*info.PolicyInfo{} diff --git a/scripts/compile-image.sh b/scripts/compile-image.sh index 9fd28f1bf0..58a9db8e42 100755 --- a/scripts/compile-image.sh +++ b/scripts/compile-image.sh @@ -13,7 +13,7 @@ hub_user_name="nirmata" project_name="kyverno" echo "# Ensuring Go dependencies..." -dep ensure || exit 2 +dep ensure -v || exit 2 echo "# Building executable ${project_name}..." chmod +x scripts/update-codegen.sh diff --git a/scripts/generate-self-signed-cert-and-k8secrets-debug.sh b/scripts/generate-self-signed-cert-and-k8secrets-debug.sh index 49c446cd24..695d265da9 100755 --- a/scripts/generate-self-signed-cert-and-k8secrets-debug.sh +++ b/scripts/generate-self-signed-cert-and-k8secrets-debug.sh @@ -55,6 +55,7 @@ openssl x509 -req -in ${destdir}/webhook.csr -CA ${destdir}/rootCA.crt -CAkey ${ kubectl delete -f definitions/install_debug.yaml 2>/dev/null +kubectl delete namespace kyverno 2>/dev/null echo "Generating corresponding kubernetes secrets for TLS pair and root CA" # create project namespace From 900ebea247c687d31e916a70e82e530528fec34c Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Fri, 26 Jul 2019 12:01:09 -0700 Subject: [PATCH 2/8] check conditions --- pkg/engine/overlayCondition.go | 153 +++++++++ pkg/engine/overlay_new.go | 70 ----- pkg/engine/overlay_test.go | 560 +++++++++++++++++++++++++++++++++ pkg/engine/utils.go | 14 + 4 files changed, 727 insertions(+), 70 deletions(-) create mode 100755 pkg/engine/overlayCondition.go delete mode 100755 pkg/engine/overlay_new.go diff --git a/pkg/engine/overlayCondition.go b/pkg/engine/overlayCondition.go new file mode 100755 index 0000000000..bd60bc3b4a --- /dev/null +++ b/pkg/engine/overlayCondition.go @@ -0,0 +1,153 @@ +package engine + +import ( + "reflect" + + "github.com/golang/glog" +) + +func meetConditions(resource, overlay interface{}) bool { + // overlay has no anchor, return true + if !hasNestedAnchors(overlay) { + return true + } + + // resource item exists but has different type + // return false if anchor exists in overlay + // conditon never be true in this case + if reflect.TypeOf(resource) != reflect.TypeOf(overlay) { + if hasNestedAnchors(overlay) { + glog.V(3).Infof("Found anchor on different types of element: overlay %T, resource %T\nSkip processing overlay.", overlay, resource) + return false + } + return true + } + + return checkConditions(resource, overlay) +} + +func checkConditions(resource, overlay interface{}) bool { + switch typedOverlay := overlay.(type) { + case map[string]interface{}: + typedResource := resource.(map[string]interface{}) + return checkConditionOnMap(typedResource, typedOverlay) + case []interface{}: + typedResource := resource.([]interface{}) + return checkConditionOnArray(typedResource, typedOverlay) + default: + return true + } +} + +func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}) bool { + anchors := getAnchorsFromMap(overlayMap) + if len(anchors) > 0 { + if !isConditionMetOnMap(resourceMap, anchors) { + return false + } + return true + } + + for key, value := range overlayMap { + resourcePart, ok := resourceMap[key] + + if ok && !isAddingAnchor(key) { + if !meetConditions(resourcePart, value) { + return false + } + } + } + + // key does not exist or isAddingAnchor + return true +} + +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 { + switch overlay[0].(type) { + case map[string]interface{}: + return checkConditionsOnArrayOfMaps(resource, overlay) + default: + 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 { + typedOverlay := overlayElement.(map[string]interface{}) + anchors, overlayWithoutAnchor := getElementsFromMap(typedOverlay) + + if len(anchors) > 0 { + if !isConditionMet(resource, anchors) { + return false + } + } + + for key, val := range overlayWithoutAnchor { + if hasNestedAnchors(val) { + for _, resourceElement := range resource { + typedResource := resourceElement.(map[string]interface{}) + + if resourcePart, ok := typedResource[key]; ok { + if !meetConditions(resourcePart, val) { + return false + } + } + } + } + } + } + return true +} + +func isConditionMet(resource []interface{}, anchors map[string]interface{}) bool { + for _, resourceElement := range resource { + typedResource := resourceElement.(map[string]interface{}) + for key, pattern := range anchors { + key = key[1 : len(key)-1] + + value, ok := typedResource[key] + if !ok { + continue + } + + if !ValidateValueWithPattern(value, pattern) { + return false + } + } + } + return true +} + +func isConditionMetOnMap(resource, anchors map[string]interface{}) bool { + for key, pattern := range anchors { + key = key[1 : len(key)-1] + + value, ok := resource[key] + if !ok { + continue + } + + if !ValidateValueWithPattern(value, pattern) { + return false + } + } + return true +} diff --git a/pkg/engine/overlay_new.go b/pkg/engine/overlay_new.go deleted file mode 100755 index 8f807212f1..0000000000 --- a/pkg/engine/overlay_new.go +++ /dev/null @@ -1,70 +0,0 @@ -package engine - -import ( - "reflect" -) - -// func processoverlay(rule kubepolicy.Rule, rawResource []byte, gvk metav1.GroupVersionKind) ([][]byte, error) { - -// var resource interface{} -// var appliedPatches [][]byte -// err := json.Unmarshal(rawResource, &resource) -// if err != nil { -// return nil, err -// } - -// patches, err := mutateResourceWithOverlay(resource, *rule.Mutation.Overlay) -// if err != nil { -// return nil, err -// } -// appliedPatches = append(appliedPatches, patches...) - -// return appliedPatches, err -// } - -func applyoverlay(resource, overlay interface{}, path string) ([][]byte, error) { - var appliedPatches [][]byte - // resource item exists but has different type - replace - // all subtree within this path by overlay - if reflect.TypeOf(resource) != reflect.TypeOf(overlay) { - patch, err := replaceSubtree(overlay, path) - if err != nil { - return nil, err - } - - appliedPatches = append(appliedPatches, patch) - } - - return applyOverlayForSameTypes(resource, overlay, path) -} - -func checkConditions(resource, overlay interface{}, path string) bool { - - switch typedOverlay := overlay.(type) { - case map[string]interface{}: - typedResource := resource.(map[string]interface{}) - if !checkConditionOnMap(typedResource, typedOverlay) { - return false - } - case []interface{}: - typedResource := resource.([]interface{}) - if !checkConditionOnArray(typedResource, typedOverlay) { - return false - } - case string, float64, int64, bool: - - default: - return false - } - return true -} - -func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}) bool { - // _ := getAnchorsFromMap(overlayMap) - - return false -} - -func checkConditionOnArray(resource, overlay []interface{}) bool { - return false -} diff --git a/pkg/engine/overlay_test.go b/pkg/engine/overlay_test.go index cb0326bc99..0eef9f64be 100644 --- a/pkg/engine/overlay_test.go +++ b/pkg/engine/overlay_test.go @@ -16,6 +16,566 @@ func compareJSONAsMap(t *testing.T, expected, actual []byte) { assert.Assert(t, reflect.DeepEqual(expectedMap, actualMap)) } +func TestMeetConditions_NoAnchor(t *testing.T) { + overlayRaw := []byte(` + { + "subsets":[ + { + "ports":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + var overlay interface{} + + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(nil, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_invalidConditionalAnchor(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + } + ] + }`) + + overlayRaw := []byte(` + { + "subsets":[ + { + "(ports)":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, !res) + + overlayRaw = []byte(` + { + "(subsets)":[ + { + "ports":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, !res) +} + +func TestMeetConditions_DifferentTypes(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + } + ] + }`) + + overlayRaw := []byte(` + { + "subsets":[ + { + "ports":[ + { + "(name)":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + // anchor exist + res := meetConditions(resource, overlay) + assert.Assert(t, !res) + + overlayRaw = []byte(` + { + "subsets":[ + { + "ports":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + json.Unmarshal(overlayRaw, &overlay) + + // no anchor + res = meetConditions(resource, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_singleAnchor(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + } + ] + }`) + + overlayRaw := []byte(` + { + "subsets":[ + { + "addresses":[ + { + "(ip)":"192.168.10.171" + } + ], + "ports":[ + { + "(name)":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_anchorsOnMetaAndSpec(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "metadata": { + "labels": { + "(app)": "nginx" + } + }, + "spec": { + "containers": [ + { + "(image)": "*:latest", + "imagePullPolicy": "IfNotPresent", + "ports": [ + { + "containerPort": 8080 + } + ] + } + ] + } + } + } + }`) + resourceRaw := []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "nginx-deployment", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:latest", + "ports": [ + { + "containerPort": 80 + } + ] + }, + { + "name": "ghost", + "image": "ghost:latest" + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) +} + +var resourceRawAnchorOnPeers = []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "psp-demo-unprivileged", + "labels": { + "app.type": "prod" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "psp" + } + }, + "template": { + "metadata": { + "labels": { + "app": "psp" + } + }, + "spec": { + "securityContext": { + "runAsNonRoot": true + }, + "containers": [ + { + "name": "sec-ctx-unprivileged", + "image": "nginxinc/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "name": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + +func TestMeetConditions_anchorsOnPeer_single(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "name": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRawAnchorOnPeers, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_anchorsOnPeer_two(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "(runAsNonRoot)": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "name": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRawAnchorOnPeers, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, res) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "image": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "(allowPrivilegeEscalation)": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "(runAsNonRoot)": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRawAnchorOnPeers, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "(allowPrivilegeEscalation)": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, res) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "(allowPrivilegeEscalation)": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "(value)": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, res) + +} func TestApplyOverlay_NestedListWithAnchor(t *testing.T) { resourceRaw := []byte(` { diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 265ec93118..afb306a164 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -128,6 +128,20 @@ func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} return result } +func getElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) { + anchors := make(map[string]interface{}) + elementsWithoutanchor := make(map[string]interface{}) + for key, value := range anchorsMap { + if isConditionAnchor(key) || isExistanceAnchor(key) { + anchors[key] = value + } else if !isAddingAnchor(key) { + elementsWithoutanchor[key] = value + } + } + + return anchors, elementsWithoutanchor +} + func getAnchorFromMap(anchorsMap map[string]interface{}) (string, interface{}) { for key, value := range anchorsMap { if isConditionAnchor(key) || isExistanceAnchor(key) { From 6bcce11735534b108be87b2fd95e8ef32d8fd42b Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Fri, 26 Jul 2019 13:41:39 -0700 Subject: [PATCH 3/8] update unit tests --- pkg/engine/overlayCondition_test.go | 569 ++++++++++++++++++++++++++++ pkg/engine/overlay_test.go | 560 --------------------------- 2 files changed, 569 insertions(+), 560 deletions(-) create mode 100644 pkg/engine/overlayCondition_test.go diff --git a/pkg/engine/overlayCondition_test.go b/pkg/engine/overlayCondition_test.go new file mode 100644 index 0000000000..9d70f7bff4 --- /dev/null +++ b/pkg/engine/overlayCondition_test.go @@ -0,0 +1,569 @@ +package engine + +import ( + "encoding/json" + "testing" + + "gotest.tools/assert" +) + +func TestMeetConditions_NoAnchor(t *testing.T) { + overlayRaw := []byte(` + { + "subsets":[ + { + "ports":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + var overlay interface{} + + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(nil, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_invalidConditionalAnchor(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + } + ] + }`) + + overlayRaw := []byte(` + { + "subsets":[ + { + "(ports)":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, !res) + + overlayRaw = []byte(` + { + "(subsets)":[ + { + "ports":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, !res) +} + +func TestMeetConditions_DifferentTypes(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + } + ] + }`) + + overlayRaw := []byte(` + { + "subsets":[ + { + "ports":[ + { + "(name)":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + // anchor exist + res := meetConditions(resource, overlay) + assert.Assert(t, !res) + + overlayRaw = []byte(` + { + "subsets":[ + { + "ports":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + json.Unmarshal(overlayRaw, &overlay) + + // no anchor + res = meetConditions(resource, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_singleAnchor(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + } + ] + }`) + + overlayRaw := []byte(` + { + "subsets":[ + { + "addresses":[ + { + "(ip)":"192.168.10.171" + } + ], + "ports":[ + { + "(name)":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_anchorsOnMetaAndSpec(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "metadata": { + "labels": { + "(app)": "nginx" + } + }, + "spec": { + "containers": [ + { + "(image)": "*:latest", + "imagePullPolicy": "IfNotPresent", + "ports": [ + { + "containerPort": 8080 + } + ] + } + ] + } + } + } + }`) + resourceRaw := []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "nginx-deployment", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:latest", + "ports": [ + { + "containerPort": 80 + } + ] + }, + { + "name": "ghost", + "image": "ghost:latest" + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) +} + +var resourceRawAnchorOnPeers = []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "psp-demo-unprivileged", + "labels": { + "app.type": "prod" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "psp" + } + }, + "template": { + "metadata": { + "labels": { + "app": "psp" + } + }, + "spec": { + "securityContext": { + "runAsNonRoot": true + }, + "containers": [ + { + "name": "sec-ctx-unprivileged", + "image": "nginxinc/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "name": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + +func TestMeetConditions_anchorsOnPeer_single(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "name": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRawAnchorOnPeers, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_anchorsOnPeer_two(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "(runAsNonRoot)": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "name": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRawAnchorOnPeers, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, res) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "image": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "(allowPrivilegeEscalation)": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, res) +} + +func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "(runAsNonRoot)": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRawAnchorOnPeers, &resource) + json.Unmarshal(overlayRaw, &overlay) + + res := meetConditions(resource, overlay) + assert.Assert(t, res) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "(allowPrivilegeEscalation)": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, res) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "(allowPrivilegeEscalation)": false + }, + "env": [ + { + "(name)": "ENV_KEY", + "(value)": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + res = meetConditions(resource, overlay) + assert.Assert(t, res) + +} diff --git a/pkg/engine/overlay_test.go b/pkg/engine/overlay_test.go index 0eef9f64be..cb0326bc99 100644 --- a/pkg/engine/overlay_test.go +++ b/pkg/engine/overlay_test.go @@ -16,566 +16,6 @@ func compareJSONAsMap(t *testing.T, expected, actual []byte) { assert.Assert(t, reflect.DeepEqual(expectedMap, actualMap)) } -func TestMeetConditions_NoAnchor(t *testing.T) { - overlayRaw := []byte(` - { - "subsets":[ - { - "ports":[ - { - "name":"secure-connection", - "port":444, - "protocol":"UDP" - } - ] - } - ] - }`) - var overlay interface{} - - json.Unmarshal(overlayRaw, &overlay) - - res := meetConditions(nil, overlay) - assert.Assert(t, res) -} - -func TestMeetConditions_invalidConditionalAnchor(t *testing.T) { - resourceRaw := []byte(` - { - "apiVersion":"v1", - "kind":"Endpoints", - "metadata":{ - "name":"test-endpoint", - "labels":{ - "label":"test" - } - }, - "subsets":[ - { - "addresses":[ - { - "ip":"192.168.10.171" - } - ], - "ports":[ - { - "name":"secure-connection", - "port":443, - "protocol":"TCP" - } - ] - } - ] - }`) - - overlayRaw := []byte(` - { - "subsets":[ - { - "(ports)":[ - { - "name":"secure-connection", - "port":444, - "protocol":"UDP" - } - ] - } - ] - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - res := meetConditions(resource, overlay) - assert.Assert(t, !res) - - overlayRaw = []byte(` - { - "(subsets)":[ - { - "ports":[ - { - "name":"secure-connection", - "port":444, - "protocol":"UDP" - } - ] - } - ] - }`) - - json.Unmarshal(overlayRaw, &overlay) - - res = meetConditions(resource, overlay) - assert.Assert(t, !res) -} - -func TestMeetConditions_DifferentTypes(t *testing.T) { - resourceRaw := []byte(` - { - "apiVersion":"v1", - "kind":"Endpoints", - "metadata":{ - "name":"test-endpoint", - }, - "subsets":[ - { - "addresses":[ - { - "ip":"192.168.10.171" - } - ], - } - ] - }`) - - overlayRaw := []byte(` - { - "subsets":[ - { - "ports":[ - { - "(name)":"secure-connection", - "port":444, - "protocol":"UDP" - } - ] - } - ] - }`) - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - // anchor exist - res := meetConditions(resource, overlay) - assert.Assert(t, !res) - - overlayRaw = []byte(` - { - "subsets":[ - { - "ports":[ - { - "name":"secure-connection", - "port":444, - "protocol":"UDP" - } - ] - } - ] - }`) - - json.Unmarshal(overlayRaw, &overlay) - - // no anchor - res = meetConditions(resource, overlay) - assert.Assert(t, res) -} - -func TestMeetConditions_singleAnchor(t *testing.T) { - resourceRaw := []byte(` - { - "apiVersion":"v1", - "kind":"Endpoints", - "metadata":{ - "name":"test-endpoint", - "labels":{ - "label":"test" - } - }, - "subsets":[ - { - "addresses":[ - { - "ip":"192.168.10.171" - } - ], - "ports":[ - { - "name":"secure-connection", - "port":443, - "protocol":"TCP" - } - ] - } - ] - }`) - - overlayRaw := []byte(` - { - "subsets":[ - { - "addresses":[ - { - "(ip)":"192.168.10.171" - } - ], - "ports":[ - { - "(name)":"secure-connection", - "port":444, - "protocol":"UDP" - } - ] - } - ] - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - res := meetConditions(resource, overlay) - assert.Assert(t, res) -} - -func TestMeetConditions_anchorsOnMetaAndSpec(t *testing.T) { - overlayRaw := []byte(`{ - "spec": { - "template": { - "metadata": { - "labels": { - "(app)": "nginx" - } - }, - "spec": { - "containers": [ - { - "(image)": "*:latest", - "imagePullPolicy": "IfNotPresent", - "ports": [ - { - "containerPort": 8080 - } - ] - } - ] - } - } - } - }`) - resourceRaw := []byte(`{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "nginx-deployment", - "labels": { - "app": "nginx" - } - }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "app": "nginx" - } - }, - "template": { - "metadata": { - "labels": { - "app": "nginx" - } - }, - "spec": { - "containers": [ - { - "name": "nginx", - "image": "nginx:latest", - "ports": [ - { - "containerPort": 80 - } - ] - }, - { - "name": "ghost", - "image": "ghost:latest" - } - ] - } - } - } - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - res := meetConditions(resource, overlay) - assert.Assert(t, res) -} - -var resourceRawAnchorOnPeers = []byte(`{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "psp-demo-unprivileged", - "labels": { - "app.type": "prod" - } - }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "app": "psp" - } - }, - "template": { - "metadata": { - "labels": { - "app": "psp" - } - }, - "spec": { - "securityContext": { - "runAsNonRoot": true - }, - "containers": [ - { - "name": "sec-ctx-unprivileged", - "image": "nginxinc/nginx-unprivileged", - "securityContext": { - "runAsNonRoot": true, - "allowPrivilegeEscalation": false - }, - "env": [ - { - "name": "ENV_KEY", - "value": "ENV_VALUE" - } - ] - } - ] - } - } - } - }`) - -func TestMeetConditions_anchorsOnPeer_single(t *testing.T) { - overlayRaw := []byte(`{ - "spec": { - "template": { - "spec": { - "containers": [ - { - "(image)": "*/nginx-unprivileged", - "securityContext": { - "runAsNonRoot": true, - "allowPrivilegeEscalation": false - }, - "env": [ - { - "name": "ENV_KEY", - "value": "ENV_VALUE" - } - ] - } - ] - } - } - } - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRawAnchorOnPeers, &resource) - json.Unmarshal(overlayRaw, &overlay) - - res := meetConditions(resource, overlay) - assert.Assert(t, res) -} - -func TestMeetConditions_anchorsOnPeer_two(t *testing.T) { - overlayRaw := []byte(`{ - "spec": { - "template": { - "spec": { - "containers": [ - { - "(image)": "*/nginx-unprivileged", - "securityContext": { - "(runAsNonRoot)": true, - "allowPrivilegeEscalation": false - }, - "env": [ - { - "name": "ENV_KEY", - "value": "ENV_VALUE" - } - ] - } - ] - } - } - } - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRawAnchorOnPeers, &resource) - json.Unmarshal(overlayRaw, &overlay) - - res := meetConditions(resource, overlay) - assert.Assert(t, res) - - overlayRaw = []byte(`{ - "spec": { - "template": { - "spec": { - "containers": [ - { - "(image)": "*/nginx-unprivileged", - "securityContext": { - "runAsNonRoot": true, - "allowPrivilegeEscalation": false - }, - "env": [ - { - "(name)": "ENV_KEY", - "value": "ENV_VALUE" - } - ] - } - ] - } - } - } - }`) - - json.Unmarshal(overlayRaw, &overlay) - - res = meetConditions(resource, overlay) - assert.Assert(t, res) - - overlayRaw = []byte(`{ - "spec": { - "template": { - "spec": { - "containers": [ - { - "image": "*/nginx-unprivileged", - "securityContext": { - "runAsNonRoot": true, - "(allowPrivilegeEscalation)": false - }, - "env": [ - { - "(name)": "ENV_KEY", - "value": "ENV_VALUE" - } - ] - } - ] - } - } - } - }`) - - json.Unmarshal(overlayRaw, &overlay) - - res = meetConditions(resource, overlay) - assert.Assert(t, res) -} - -func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) { - overlayRaw := []byte(`{ - "spec": { - "template": { - "spec": { - "containers": [ - { - "(image)": "*/nginx-unprivileged", - "securityContext": { - "(runAsNonRoot)": true, - "allowPrivilegeEscalation": false - }, - "env": [ - { - "(name)": "ENV_KEY", - "value": "ENV_VALUE" - } - ] - } - ] - } - } - } - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRawAnchorOnPeers, &resource) - json.Unmarshal(overlayRaw, &overlay) - - res := meetConditions(resource, overlay) - assert.Assert(t, res) - - overlayRaw = []byte(`{ - "spec": { - "template": { - "spec": { - "containers": [ - { - "(image)": "*/nginx-unprivileged", - "securityContext": { - "runAsNonRoot": true, - "(allowPrivilegeEscalation)": false - }, - "env": [ - { - "(name)": "ENV_KEY", - "value": "ENV_VALUE" - } - ] - } - ] - } - } - } - }`) - - json.Unmarshal(overlayRaw, &overlay) - - res = meetConditions(resource, overlay) - assert.Assert(t, res) - - overlayRaw = []byte(`{ - "spec": { - "template": { - "spec": { - "containers": [ - { - "(image)": "*/nginx-unprivileged", - "securityContext": { - "runAsNonRoot": true, - "(allowPrivilegeEscalation)": false - }, - "env": [ - { - "(name)": "ENV_KEY", - "(value)": "ENV_VALUE" - } - ] - } - ] - } - } - } - }`) - - json.Unmarshal(overlayRaw, &overlay) - - res = meetConditions(resource, overlay) - assert.Assert(t, res) - -} func TestApplyOverlay_NestedListWithAnchor(t *testing.T) { resourceRaw := []byte(` { From ff45794455f9691a4c3d25aa9d2aba50599f49e2 Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Fri, 26 Jul 2019 15:54:42 -0700 Subject: [PATCH 4/8] patch overlay --- pkg/engine/overlayCondition.go | 19 +- pkg/engine/overlayPatch.go | 35 ++ pkg/engine/overlayPatch_test.go | 587 ++++++++++++++++++++++++++++++++ pkg/engine/utils.go | 9 + 4 files changed, 648 insertions(+), 2 deletions(-) create mode 100644 pkg/engine/overlayPatch.go create mode 100644 pkg/engine/overlayPatch_test.go diff --git a/pkg/engine/overlayCondition.go b/pkg/engine/overlayCondition.go index bd60bc3b4a..ac5e062182 100755 --- a/pkg/engine/overlayCondition.go +++ b/pkg/engine/overlayCondition.go @@ -95,6 +95,10 @@ func checkConditionsOnArrayOfMaps(resource, overlay []interface{}) bool { anchors, overlayWithoutAnchor := getElementsFromMap(typedOverlay) if len(anchors) > 0 { + if !validAnchorMap(anchors) { + return false + } + if !isConditionMet(resource, anchors) { return false } @@ -117,6 +121,17 @@ func checkConditionsOnArrayOfMaps(resource, overlay []interface{}) bool { return true } +func validAnchorMap(anchors map[string]interface{}) bool { + for _, val := range anchors { + switch val.(type) { + case map[string]interface{}, []interface{}: + glog.Warning("Maps and arrays as patterns are not supported") + return false + } + } + return true +} + func isConditionMet(resource []interface{}, anchors map[string]interface{}) bool { for _, resourceElement := range resource { typedResource := resourceElement.(map[string]interface{}) @@ -128,8 +143,8 @@ func isConditionMet(resource []interface{}, anchors map[string]interface{}) bool continue } - if !ValidateValueWithPattern(value, pattern) { - return false + if ValidateValueWithPattern(value, pattern) { + return true } } } diff --git a/pkg/engine/overlayPatch.go b/pkg/engine/overlayPatch.go new file mode 100644 index 0000000000..2a435d85a8 --- /dev/null +++ b/pkg/engine/overlayPatch.go @@ -0,0 +1,35 @@ +package engine + +import ( + "encoding/json" + "errors" + "strings" + + "github.com/golang/glog" + kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" +) + +func patchOverlay(rule kubepolicy.Rule, rawResource []byte) ([][]byte, error) { + var resource interface{} + if err := json.Unmarshal(rawResource, &resource); err != nil { + return nil, err + } + + resourceInfo := ParseResourceInfoFromObject(rawResource) + patches, err := processOverlayPatches(resource, *rule.Mutation.Overlay) + if err != nil && strings.Contains(err.Error(), "Conditions are not met") { + glog.Infof("Resource does not meet conditions in overlay pattern, resource=%s, rule=%s\n", resourceInfo, rule.Name) + return nil, nil + } + + return patches, err +} + +func processOverlayPatches(resource, overlay interface{}) ([][]byte, error) { + + if !meetConditions(resource, overlay) { + return nil, errors.New("Conditions are not met") + } + + return mutateResourceWithOverlay(resource, overlay) +} diff --git a/pkg/engine/overlayPatch_test.go b/pkg/engine/overlayPatch_test.go new file mode 100644 index 0000000000..4d07c5da13 --- /dev/null +++ b/pkg/engine/overlayPatch_test.go @@ -0,0 +1,587 @@ +package engine + +import ( + "encoding/json" + "testing" + + jsonpatch "github.com/evanphx/json-patch" + "gotest.tools/assert" +) + +func TestProcessOverlayPatches_NestedListWithAnchor(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + } + ] + }`) + + overlayRaw := []byte(` + { + "subsets":[ + { + "ports":[ + { + "(name)":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + patches, err := processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, patches != nil) + + patch := JoinPatches(patches) + decoded, err := jsonpatch.DecodePatch(patch) + assert.NilError(t, err) + assert.Assert(t, decoded != nil) + + patched, err := decoded.Apply(resourceRaw) + assert.NilError(t, err) + assert.Assert(t, patched != nil) + + expectedResult := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":444.000000, + "protocol":"UDP" + } + ] + } + ] + }`) + + compareJSONAsMap(t, expectedResult, patched) +} + +func TestProcessOverlayPatches_InsertIntoArray(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + } + ] + }`) + overlayRaw := []byte(` + { + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.172" + }, + { + "ip":"192.168.10.173" + } + ], + "ports":[ + { + "name":"insecure-connection", + "port":80, + "protocol":"UDP" + } + ] + } + ] + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + patches, err := processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, patches != nil) + + patch := JoinPatches(patches) + + decoded, err := jsonpatch.DecodePatch(patch) + assert.NilError(t, err) + assert.Assert(t, decoded != nil) + + patched, err := decoded.Apply(resourceRaw) + assert.NilError(t, err) + assert.Assert(t, patched != nil) + + expectedResult := []byte(`{ + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + }, + { + "addresses":[ + { + "ip":"192.168.10.172" + }, + { + "ip":"192.168.10.173" + } + ], + "ports":[ + { + "name":"insecure-connection", + "port":80, + "protocol":"UDP" + } + ] + } + ] + }`) + + compareJSONAsMap(t, expectedResult, patched) +} + +func TestProcessOverlayPatches_TestInsertToArray(t *testing.T) { + overlayRaw := []byte(` + { + "spec":{ + "template":{ + "spec":{ + "containers":[ + { + "name":"pi1", + "image":"vasylev.perl" + } + ] + } + } + } + }`) + resourceRaw := []byte(`{ + "apiVersion":"batch/v1", + "kind":"Job", + "metadata":{ + "name":"pi" + }, + "spec":{ + "template":{ + "spec":{ + "containers":[ + { + "name":"piv0", + "image":"perl", + "command":[ + "perl" + ] + }, + { + "name":"pi", + "image":"perl", + "command":[ + "perl" + ] + }, + { + "name":"piv1", + "image":"perl", + "command":[ + "perl" + ] + } + ], + "restartPolicy":"Never" + } + }, + "backoffLimit":4 + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + patches, err := processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, patches != nil) + + patch := JoinPatches(patches) + + decoded, err := jsonpatch.DecodePatch(patch) + assert.NilError(t, err) + assert.Assert(t, decoded != nil) + + patched, err := decoded.Apply(resourceRaw) + assert.NilError(t, err) + assert.Assert(t, patched != nil) +} + +func TestProcessOverlayPatches_ImagePullPolicy(t *testing.T) { + overlayRaw := []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*:latest", + "imagePullPolicy": "IfNotPresent", + "ports": [ + { + "containerPort": 8080 + } + ] + } + ] + } + } + } + }`) + resourceRaw := []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "nginx-deployment", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:latest", + "ports": [ + { + "containerPort": 80 + } + ] + }, + { + "name": "ghost", + "image": "ghost:latest" + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + patches, err := processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, len(patches) != 0) + + doc, err := ApplyPatches(resourceRaw, patches) + assert.NilError(t, err) + expectedResult := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "replicas":1, + "selector":{ + "matchLabels":{ + "app":"nginx" + } + }, + "template":{ + "metadata":{ + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "image":"nginx:latest", + "imagePullPolicy":"IfNotPresent", + "name":"nginx", + "ports":[ + { + "containerPort":80 + }, + { + "containerPort":8080 + } + ] + }, + { + "image":"ghost:latest", + "imagePullPolicy":"IfNotPresent", + "name":"ghost", + "ports":[ + { + "containerPort":8080 + } + ] + } + ] + } + } + } + }`) + + compareJSONAsMap(t, expectedResult, doc) +} + +func TestProcessOverlayPatches_AddingAnchor(t *testing.T) { + overlayRaw := []byte(`{ + "metadata": { + "name": "nginx-deployment", + "labels": { + "+(app)": "should-not-be-here", + "+(key1)": "value1" + } + } + }`) + resourceRaw := []byte(`{ + "metadata": { + "name": "nginx-deployment", + "labels": { + "app": "nginx" + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + patches, err := processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, len(patches) != 0) + + doc, err := ApplyPatches(resourceRaw, patches) + assert.NilError(t, err) + expectedResult := []byte(`{ + "metadata":{ + "labels":{ + "app":"nginx", + "key1":"value1" + }, + "name":"nginx-deployment" + } + }`) + + compareJSONAsMap(t, expectedResult, doc) +} + +func TestProcessOverlayPatches_AddingAnchorInsideListElement(t *testing.T) { + overlayRaw := []byte(` + { + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*:latest", + "+(imagePullPolicy)": "IfNotPresent" + } + ] + } + } + } + }`) + resourceRaw := []byte(` + { + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "replicas":1, + "selector":{ + "matchLabels":{ + "app":"nginx" + } + }, + "template":{ + "metadata":{ + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "image":"nginx:latest" + }, + { + "image":"ghost:latest", + "imagePullPolicy":"Always" + }, + { + "image":"debian:10" + }, + { + "image":"ubuntu:18.04", + "imagePullPolicy":"Always" + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + patches, err := processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, len(patches) != 0) + + doc, err := ApplyPatches(resourceRaw, patches) + assert.NilError(t, err) + expectedResult := []byte(` + { + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "replicas":1, + "selector":{ + "matchLabels":{ + "app":"nginx" + } + }, + "template":{ + "metadata":{ + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "image":"nginx:latest", + "imagePullPolicy":"IfNotPresent" + }, + { + "image":"ghost:latest", + "imagePullPolicy":"Always" + }, + { + "image":"debian:10" + }, + { + "image":"ubuntu:18.04", + "imagePullPolicy":"Always" + } + ] + } + } + } + }`) + compareJSONAsMap(t, expectedResult, doc) +} diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 0db89300ee..2cbdb4ca70 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -93,6 +93,15 @@ func parseMetadataFromObject(bytes []byte) map[string]interface{} { return meta } +// ParseResourceInfoFromObject get kind/namepace/name from resource +func ParseResourceInfoFromObject(rawResource []byte) string { + + kind := ParseKindFromObject(rawResource) + namespace := ParseNamespaceFromObject(rawResource) + name := ParseNameFromObject(rawResource) + return strings.Join([]string{kind, namespace, name}, "/") +} + //ParseKindFromObject get kind from resource func ParseKindFromObject(bytes []byte) string { var objectJSON map[string]interface{} From a70d253828ae7de42a48534c4861b646260229f3 Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Fri, 26 Jul 2019 17:52:12 -0700 Subject: [PATCH 5/8] fix anchors in the list of map VS anchors on different list --- pkg/engine/overlay.go | 25 +- pkg/engine/overlayCondition.go | 7 +- pkg/engine/overlayPatch.go | 10 - pkg/engine/overlayPatch_test.go | 587 -------------------------------- 4 files changed, 23 insertions(+), 606 deletions(-) delete mode 100644 pkg/engine/overlayPatch_test.go diff --git a/pkg/engine/overlay.go b/pkg/engine/overlay.go index a0107b7799..7fea8f4cec 100644 --- a/pkg/engine/overlay.go +++ b/pkg/engine/overlay.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "github.com/golang/glog" @@ -19,19 +20,27 @@ import ( func ProcessOverlay(rule kubepolicy.Rule, rawResource []byte, gvk metav1.GroupVersionKind) ([][]byte, error) { var resource interface{} - var appliedPatches [][]byte - err := json.Unmarshal(rawResource, &resource) - if err != nil { + if err := json.Unmarshal(rawResource, &resource); err != nil { return nil, err } - patches, err := mutateResourceWithOverlay(resource, *rule.Mutation.Overlay) - if err != nil { - return nil, err + resourceInfo := ParseResourceInfoFromObject(rawResource) + patches, err := processOverlayPatches(resource, *rule.Mutation.Overlay) + if err != nil && strings.Contains(err.Error(), "Conditions are not met") { + glog.Infof("Resource does not meet conditions in overlay pattern, resource=%s, rule=%s\n", resourceInfo, rule.Name) + return nil, nil } - appliedPatches = append(appliedPatches, patches...) - return appliedPatches, err + return patches, err +} + +func processOverlayPatches(resource, overlay interface{}) ([][]byte, error) { + + if !meetConditions(resource, overlay) { + return nil, errors.New("Conditions are not met") + } + + return mutateResourceWithOverlay(resource, overlay) } // mutateResourceWithOverlay is a start of overlaying process diff --git a/pkg/engine/overlayCondition.go b/pkg/engine/overlayCondition.go index ac5e062182..c08128d699 100755 --- a/pkg/engine/overlayCondition.go +++ b/pkg/engine/overlayCondition.go @@ -143,7 +143,12 @@ func isConditionMet(resource []interface{}, anchors map[string]interface{}) bool continue } - if ValidateValueWithPattern(value, pattern) { + if len(resource) == 1 { + if !ValidateValueWithPattern(value, pattern) { + return false + } + } else { + ValidateValueWithPattern(value, pattern) return true } } diff --git a/pkg/engine/overlayPatch.go b/pkg/engine/overlayPatch.go index 2a435d85a8..73c8ad9f47 100644 --- a/pkg/engine/overlayPatch.go +++ b/pkg/engine/overlayPatch.go @@ -2,7 +2,6 @@ package engine import ( "encoding/json" - "errors" "strings" "github.com/golang/glog" @@ -24,12 +23,3 @@ func patchOverlay(rule kubepolicy.Rule, rawResource []byte) ([][]byte, error) { return patches, err } - -func processOverlayPatches(resource, overlay interface{}) ([][]byte, error) { - - if !meetConditions(resource, overlay) { - return nil, errors.New("Conditions are not met") - } - - return mutateResourceWithOverlay(resource, overlay) -} diff --git a/pkg/engine/overlayPatch_test.go b/pkg/engine/overlayPatch_test.go deleted file mode 100644 index 4d07c5da13..0000000000 --- a/pkg/engine/overlayPatch_test.go +++ /dev/null @@ -1,587 +0,0 @@ -package engine - -import ( - "encoding/json" - "testing" - - jsonpatch "github.com/evanphx/json-patch" - "gotest.tools/assert" -) - -func TestProcessOverlayPatches_NestedListWithAnchor(t *testing.T) { - resourceRaw := []byte(` - { - "apiVersion":"v1", - "kind":"Endpoints", - "metadata":{ - "name":"test-endpoint", - "labels":{ - "label":"test" - } - }, - "subsets":[ - { - "addresses":[ - { - "ip":"192.168.10.171" - } - ], - "ports":[ - { - "name":"secure-connection", - "port":443, - "protocol":"TCP" - } - ] - } - ] - }`) - - overlayRaw := []byte(` - { - "subsets":[ - { - "ports":[ - { - "(name)":"secure-connection", - "port":444, - "protocol":"UDP" - } - ] - } - ] - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - patches, err := processOverlayPatches(resource, overlay) - assert.NilError(t, err) - assert.Assert(t, patches != nil) - - patch := JoinPatches(patches) - decoded, err := jsonpatch.DecodePatch(patch) - assert.NilError(t, err) - assert.Assert(t, decoded != nil) - - patched, err := decoded.Apply(resourceRaw) - assert.NilError(t, err) - assert.Assert(t, patched != nil) - - expectedResult := []byte(` - { - "apiVersion":"v1", - "kind":"Endpoints", - "metadata":{ - "name":"test-endpoint", - "labels":{ - "label":"test" - } - }, - "subsets":[ - { - "addresses":[ - { - "ip":"192.168.10.171" - } - ], - "ports":[ - { - "name":"secure-connection", - "port":444.000000, - "protocol":"UDP" - } - ] - } - ] - }`) - - compareJSONAsMap(t, expectedResult, patched) -} - -func TestProcessOverlayPatches_InsertIntoArray(t *testing.T) { - resourceRaw := []byte(` - { - "apiVersion":"v1", - "kind":"Endpoints", - "metadata":{ - "name":"test-endpoint", - "labels":{ - "label":"test" - } - }, - "subsets":[ - { - "addresses":[ - { - "ip":"192.168.10.171" - } - ], - "ports":[ - { - "name":"secure-connection", - "port":443, - "protocol":"TCP" - } - ] - } - ] - }`) - overlayRaw := []byte(` - { - "subsets":[ - { - "addresses":[ - { - "ip":"192.168.10.172" - }, - { - "ip":"192.168.10.173" - } - ], - "ports":[ - { - "name":"insecure-connection", - "port":80, - "protocol":"UDP" - } - ] - } - ] - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - patches, err := processOverlayPatches(resource, overlay) - assert.NilError(t, err) - assert.Assert(t, patches != nil) - - patch := JoinPatches(patches) - - decoded, err := jsonpatch.DecodePatch(patch) - assert.NilError(t, err) - assert.Assert(t, decoded != nil) - - patched, err := decoded.Apply(resourceRaw) - assert.NilError(t, err) - assert.Assert(t, patched != nil) - - expectedResult := []byte(`{ - "apiVersion":"v1", - "kind":"Endpoints", - "metadata":{ - "name":"test-endpoint", - "labels":{ - "label":"test" - } - }, - "subsets":[ - { - "addresses":[ - { - "ip":"192.168.10.171" - } - ], - "ports":[ - { - "name":"secure-connection", - "port":443, - "protocol":"TCP" - } - ] - }, - { - "addresses":[ - { - "ip":"192.168.10.172" - }, - { - "ip":"192.168.10.173" - } - ], - "ports":[ - { - "name":"insecure-connection", - "port":80, - "protocol":"UDP" - } - ] - } - ] - }`) - - compareJSONAsMap(t, expectedResult, patched) -} - -func TestProcessOverlayPatches_TestInsertToArray(t *testing.T) { - overlayRaw := []byte(` - { - "spec":{ - "template":{ - "spec":{ - "containers":[ - { - "name":"pi1", - "image":"vasylev.perl" - } - ] - } - } - } - }`) - resourceRaw := []byte(`{ - "apiVersion":"batch/v1", - "kind":"Job", - "metadata":{ - "name":"pi" - }, - "spec":{ - "template":{ - "spec":{ - "containers":[ - { - "name":"piv0", - "image":"perl", - "command":[ - "perl" - ] - }, - { - "name":"pi", - "image":"perl", - "command":[ - "perl" - ] - }, - { - "name":"piv1", - "image":"perl", - "command":[ - "perl" - ] - } - ], - "restartPolicy":"Never" - } - }, - "backoffLimit":4 - } - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - patches, err := processOverlayPatches(resource, overlay) - assert.NilError(t, err) - assert.Assert(t, patches != nil) - - patch := JoinPatches(patches) - - decoded, err := jsonpatch.DecodePatch(patch) - assert.NilError(t, err) - assert.Assert(t, decoded != nil) - - patched, err := decoded.Apply(resourceRaw) - assert.NilError(t, err) - assert.Assert(t, patched != nil) -} - -func TestProcessOverlayPatches_ImagePullPolicy(t *testing.T) { - overlayRaw := []byte(`{ - "spec": { - "template": { - "spec": { - "containers": [ - { - "(image)": "*:latest", - "imagePullPolicy": "IfNotPresent", - "ports": [ - { - "containerPort": 8080 - } - ] - } - ] - } - } - } - }`) - resourceRaw := []byte(`{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "nginx-deployment", - "labels": { - "app": "nginx" - } - }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "app": "nginx" - } - }, - "template": { - "metadata": { - "labels": { - "app": "nginx" - } - }, - "spec": { - "containers": [ - { - "name": "nginx", - "image": "nginx:latest", - "ports": [ - { - "containerPort": 80 - } - ] - }, - { - "name": "ghost", - "image": "ghost:latest" - } - ] - } - } - } - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - patches, err := processOverlayPatches(resource, overlay) - assert.NilError(t, err) - assert.Assert(t, len(patches) != 0) - - doc, err := ApplyPatches(resourceRaw, patches) - assert.NilError(t, err) - expectedResult := []byte(`{ - "apiVersion":"apps/v1", - "kind":"Deployment", - "metadata":{ - "name":"nginx-deployment", - "labels":{ - "app":"nginx" - } - }, - "spec":{ - "replicas":1, - "selector":{ - "matchLabels":{ - "app":"nginx" - } - }, - "template":{ - "metadata":{ - "labels":{ - "app":"nginx" - } - }, - "spec":{ - "containers":[ - { - "image":"nginx:latest", - "imagePullPolicy":"IfNotPresent", - "name":"nginx", - "ports":[ - { - "containerPort":80 - }, - { - "containerPort":8080 - } - ] - }, - { - "image":"ghost:latest", - "imagePullPolicy":"IfNotPresent", - "name":"ghost", - "ports":[ - { - "containerPort":8080 - } - ] - } - ] - } - } - } - }`) - - compareJSONAsMap(t, expectedResult, doc) -} - -func TestProcessOverlayPatches_AddingAnchor(t *testing.T) { - overlayRaw := []byte(`{ - "metadata": { - "name": "nginx-deployment", - "labels": { - "+(app)": "should-not-be-here", - "+(key1)": "value1" - } - } - }`) - resourceRaw := []byte(`{ - "metadata": { - "name": "nginx-deployment", - "labels": { - "app": "nginx" - } - } - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - patches, err := processOverlayPatches(resource, overlay) - assert.NilError(t, err) - assert.Assert(t, len(patches) != 0) - - doc, err := ApplyPatches(resourceRaw, patches) - assert.NilError(t, err) - expectedResult := []byte(`{ - "metadata":{ - "labels":{ - "app":"nginx", - "key1":"value1" - }, - "name":"nginx-deployment" - } - }`) - - compareJSONAsMap(t, expectedResult, doc) -} - -func TestProcessOverlayPatches_AddingAnchorInsideListElement(t *testing.T) { - overlayRaw := []byte(` - { - "spec": { - "template": { - "spec": { - "containers": [ - { - "(image)": "*:latest", - "+(imagePullPolicy)": "IfNotPresent" - } - ] - } - } - } - }`) - resourceRaw := []byte(` - { - "apiVersion":"apps/v1", - "kind":"Deployment", - "metadata":{ - "name":"nginx-deployment", - "labels":{ - "app":"nginx" - } - }, - "spec":{ - "replicas":1, - "selector":{ - "matchLabels":{ - "app":"nginx" - } - }, - "template":{ - "metadata":{ - "labels":{ - "app":"nginx" - } - }, - "spec":{ - "containers":[ - { - "image":"nginx:latest" - }, - { - "image":"ghost:latest", - "imagePullPolicy":"Always" - }, - { - "image":"debian:10" - }, - { - "image":"ubuntu:18.04", - "imagePullPolicy":"Always" - } - ] - } - } - } - }`) - - var resource, overlay interface{} - - json.Unmarshal(resourceRaw, &resource) - json.Unmarshal(overlayRaw, &overlay) - - patches, err := processOverlayPatches(resource, overlay) - assert.NilError(t, err) - assert.Assert(t, len(patches) != 0) - - doc, err := ApplyPatches(resourceRaw, patches) - assert.NilError(t, err) - expectedResult := []byte(` - { - "apiVersion":"apps/v1", - "kind":"Deployment", - "metadata":{ - "name":"nginx-deployment", - "labels":{ - "app":"nginx" - } - }, - "spec":{ - "replicas":1, - "selector":{ - "matchLabels":{ - "app":"nginx" - } - }, - "template":{ - "metadata":{ - "labels":{ - "app":"nginx" - } - }, - "spec":{ - "containers":[ - { - "image":"nginx:latest", - "imagePullPolicy":"IfNotPresent" - }, - { - "image":"ghost:latest", - "imagePullPolicy":"Always" - }, - { - "image":"debian:10" - }, - { - "image":"ubuntu:18.04", - "imagePullPolicy":"Always" - } - ] - } - } - } - }`) - compareJSONAsMap(t, expectedResult, doc) -} From 590aa74bd847b7b0607c5277183fcec89f673af1 Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Fri, 26 Jul 2019 17:52:26 -0700 Subject: [PATCH 6/8] update unit tests --- pkg/engine/overlayCondition_test.go | 45 +++++- pkg/engine/overlay_test.go | 240 ++++++++++++++++++++++++++-- 2 files changed, 267 insertions(+), 18 deletions(-) diff --git a/pkg/engine/overlayCondition_test.go b/pkg/engine/overlayCondition_test.go index 9d70f7bff4..f1cfe250fb 100644 --- a/pkg/engine/overlayCondition_test.go +++ b/pkg/engine/overlayCondition_test.go @@ -144,15 +144,45 @@ func TestMeetConditions_DifferentTypes(t *testing.T) { // anchor exist res := meetConditions(resource, overlay) assert.Assert(t, !res) +} - overlayRaw = []byte(` +func TestMeetConditions_anchosInSameObject(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + } + ] + }`) + + overlayRaw := []byte(` { "subsets":[ { "ports":[ { - "name":"secure-connection", - "port":444, + "(name)":"secure-connection", + "(port)":444, "protocol":"UDP" } ] @@ -160,14 +190,17 @@ func TestMeetConditions_DifferentTypes(t *testing.T) { ] }`) + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) json.Unmarshal(overlayRaw, &overlay) // no anchor - res = meetConditions(resource, overlay) - assert.Assert(t, res) + res := meetConditions(resource, overlay) + assert.Assert(t, !res) } -func TestMeetConditions_singleAnchor(t *testing.T) { +func TestMeetConditions_anchorOnPeer(t *testing.T) { resourceRaw := []byte(` { "apiVersion":"v1", diff --git a/pkg/engine/overlay_test.go b/pkg/engine/overlay_test.go index cb0326bc99..1de11f88d4 100644 --- a/pkg/engine/overlay_test.go +++ b/pkg/engine/overlay_test.go @@ -16,7 +16,7 @@ func compareJSONAsMap(t *testing.T, expected, actual []byte) { assert.Assert(t, reflect.DeepEqual(expectedMap, actualMap)) } -func TestApplyOverlay_NestedListWithAnchor(t *testing.T) { +func TestProcessOverlayPatches_NestedListWithAnchor(t *testing.T) { resourceRaw := []byte(` { "apiVersion":"v1", @@ -65,7 +65,7 @@ func TestApplyOverlay_NestedListWithAnchor(t *testing.T) { json.Unmarshal(resourceRaw, &resource) json.Unmarshal(overlayRaw, &overlay) - patches, err := applyOverlay(resource, overlay, "/") + patches, err := processOverlayPatches(resource, overlay) assert.NilError(t, err) assert.Assert(t, patches != nil) @@ -109,7 +109,7 @@ func TestApplyOverlay_NestedListWithAnchor(t *testing.T) { compareJSONAsMap(t, expectedResult, patched) } -func TestApplyOverlay_InsertIntoArray(t *testing.T) { +func TestProcessOverlayPatches_InsertIntoArray(t *testing.T) { resourceRaw := []byte(` { "apiVersion":"v1", @@ -165,7 +165,7 @@ func TestApplyOverlay_InsertIntoArray(t *testing.T) { json.Unmarshal(resourceRaw, &resource) json.Unmarshal(overlayRaw, &overlay) - patches, err := applyOverlay(resource, overlay, "/") + patches, err := processOverlayPatches(resource, overlay) assert.NilError(t, err) assert.Assert(t, patches != nil) @@ -226,7 +226,7 @@ func TestApplyOverlay_InsertIntoArray(t *testing.T) { compareJSONAsMap(t, expectedResult, patched) } -func TestApplyOverlay_TestInsertToArray(t *testing.T) { +func TestProcessOverlayPatches_TestInsertToArray(t *testing.T) { overlayRaw := []byte(` { "spec":{ @@ -286,7 +286,7 @@ func TestApplyOverlay_TestInsertToArray(t *testing.T) { json.Unmarshal(resourceRaw, &resource) json.Unmarshal(overlayRaw, &overlay) - patches, err := applyOverlay(resource, overlay, "/") + patches, err := processOverlayPatches(resource, overlay) assert.NilError(t, err) assert.Assert(t, patches != nil) @@ -301,7 +301,7 @@ func TestApplyOverlay_TestInsertToArray(t *testing.T) { assert.Assert(t, patched != nil) } -func TestApplyOverlay_ImagePullPolicy(t *testing.T) { +func TestProcessOverlayPatches_ImagePullPolicy(t *testing.T) { overlayRaw := []byte(`{ "spec": { "template": { @@ -369,7 +369,7 @@ func TestApplyOverlay_ImagePullPolicy(t *testing.T) { json.Unmarshal(resourceRaw, &resource) json.Unmarshal(overlayRaw, &overlay) - patches, err := applyOverlay(resource, overlay, "/") + patches, err := processOverlayPatches(resource, overlay) assert.NilError(t, err) assert.Assert(t, len(patches) != 0) @@ -429,9 +429,76 @@ func TestApplyOverlay_ImagePullPolicy(t *testing.T) { }`) compareJSONAsMap(t, expectedResult, doc) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "metadata": { + "labels": { + "(app)": "nginx" + } + }, + "spec": { + "containers": [ + { + "(image)": "*:latest", + "imagePullPolicy": "IfNotPresent", + "ports": [ + { + "containerPort": 8080 + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + patches, err = processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, len(patches) != 0) + + doc, err = ApplyPatches(resourceRaw, patches) + assert.NilError(t, err) + + compareJSONAsMap(t, expectedResult, doc) + + overlayRaw = []byte(`{ + "spec": { + "template": { + "metadata": { + "labels": { + "(app)": "nginx1" + } + }, + "spec": { + "containers": [ + { + "(image)": "*:latest", + "imagePullPolicy": "IfNotPresent", + "ports": [ + { + "containerPort": 8080 + } + ] + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + patches, err = processOverlayPatches(resource, overlay) + assert.Error(t, err, "Conditions are not met") + assert.Assert(t, len(patches) == 0) } -func TestApplyOverlay_AddingAnchor(t *testing.T) { +func TestProcessOverlayPatches_AddingAnchor(t *testing.T) { overlayRaw := []byte(`{ "metadata": { "name": "nginx-deployment", @@ -455,7 +522,7 @@ func TestApplyOverlay_AddingAnchor(t *testing.T) { json.Unmarshal(resourceRaw, &resource) json.Unmarshal(overlayRaw, &overlay) - patches, err := applyOverlay(resource, overlay, "/") + patches, err := processOverlayPatches(resource, overlay) assert.NilError(t, err) assert.Assert(t, len(patches) != 0) @@ -474,7 +541,7 @@ func TestApplyOverlay_AddingAnchor(t *testing.T) { compareJSONAsMap(t, expectedResult, doc) } -func TestApplyOverlay_AddingAnchorInsideListElement(t *testing.T) { +func TestProcessOverlayPatches_AddingAnchorInsideListElement(t *testing.T) { overlayRaw := []byte(` { "spec": { @@ -540,7 +607,7 @@ func TestApplyOverlay_AddingAnchorInsideListElement(t *testing.T) { json.Unmarshal(resourceRaw, &resource) json.Unmarshal(overlayRaw, &overlay) - patches, err := applyOverlay(resource, overlay, "/") + patches, err := processOverlayPatches(resource, overlay) assert.NilError(t, err) assert.Assert(t, len(patches) != 0) @@ -592,4 +659,153 @@ func TestApplyOverlay_AddingAnchorInsideListElement(t *testing.T) { } }`) compareJSONAsMap(t, expectedResult, doc) + + // multiple anchors + overlayRaw = []byte(` + { + "spec": { + "template": { + "metadata": { + "labels": { + "(app)": "nginx" + } + }, + "spec": { + "containers": [ + { + "(image)": "*:latest", + "+(imagePullPolicy)": "IfNotPresent" + } + ] + } + } + } + }`) + + json.Unmarshal(overlayRaw, &overlay) + + patches, err = processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, len(patches) != 0) + + doc, err = ApplyPatches(resourceRaw, patches) + assert.NilError(t, err) + + compareJSONAsMap(t, expectedResult, doc) +} + +func TestProcessOverlayPatches_anchorOnPeer(t *testing.T) { + resourceRaw := []byte(` + { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":443, + "protocol":"TCP" + } + ] + } + ] + }`) + + overlayRaw := []byte(` + { + "subsets":[ + { + "addresses":[ + { + "(ip)":"192.168.10.171" + } + ], + "ports":[ + { + "(name)":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRaw, &resource) + json.Unmarshal(overlayRaw, &overlay) + + patches, err := processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, len(patches) != 0) + + doc, err := ApplyPatches(resourceRaw, patches) + assert.NilError(t, err) + expectedResult := []byte(` { + "apiVersion":"v1", + "kind":"Endpoints", + "metadata":{ + "name":"test-endpoint", + "labels":{ + "label":"test" + } + }, + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "name":"secure-connection", + "port":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + compareJSONAsMap(t, expectedResult, doc) + + overlayRaw = []byte(` + { + "subsets":[ + { + "addresses":[ + { + "ip":"192.168.10.171" + } + ], + "ports":[ + { + "(name)":"secure-connection", + "(port)":444, + "protocol":"UDP" + } + ] + } + ] + }`) + + json.Unmarshal(overlayRaw, &overlay) + + patches, err = processOverlayPatches(resource, overlay) + assert.Error(t, err, "Conditions are not met") + assert.Assert(t, len(patches) == 0) } From d32f88f981c79523f694400e3a8283da11987e18 Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Fri, 26 Jul 2019 18:27:59 -0700 Subject: [PATCH 7/8] add unit test case --- pkg/engine/overlay_test.go | 137 +++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/pkg/engine/overlay_test.go b/pkg/engine/overlay_test.go index 1de11f88d4..134da5f027 100644 --- a/pkg/engine/overlay_test.go +++ b/pkg/engine/overlay_test.go @@ -809,3 +809,140 @@ func TestProcessOverlayPatches_anchorOnPeer(t *testing.T) { assert.Error(t, err, "Conditions are not met") assert.Assert(t, len(patches) == 0) } + +func TestProcessOverlayPatches_insertWithCondition(t *testing.T) { + resourceRaw := []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "psp-demo-unprivileged", + "labels": { + "app.type": "prod" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "psp" + } + }, + "template": { + "metadata": { + "labels": { + "app": "psp" + } + }, + "spec": { + "securityContext": { + "runAsNonRoot": true + }, + "containers": [ + { + "name": "sec-ctx-unprivileged", + "image": "nginxinc/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": false + }, + "env": [ + { + "name": "ENV_KEY", + "value": "ENV_VALUE" + } + ] + } + ] + } + } + } + }`) + + overlayRaw := []byte(`{ + "spec": { + "template": { + "spec": { + "containers": [ + { + "(image)": "*/nginx-unprivileged", + "securityContext": { + "(runAsNonRoot)": true, + "allowPrivilegeEscalation": true + }, + "env": [ + { + "name": "ENV_NEW_KEY", + "value": "ENV_NEW_VALUE" + } + ] + } + ] + } + } + } + }`) + + var resource, overlay interface{} + + json.Unmarshal(resourceRawAnchorOnPeers, &resource) + json.Unmarshal(overlayRaw, &overlay) + + patches, err := processOverlayPatches(resource, overlay) + assert.NilError(t, err) + assert.Assert(t, len(patches) != 0) + + doc, err := ApplyPatches(resourceRaw, patches) + assert.NilError(t, err) + expectedResult := []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "psp-demo-unprivileged", + "labels": { + "app.type": "prod" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "psp" + } + }, + "template": { + "metadata": { + "labels": { + "app": "psp" + } + }, + "spec": { + "securityContext": { + "runAsNonRoot": true + }, + "containers": [ + { + "name": "sec-ctx-unprivileged", + "image": "nginxinc/nginx-unprivileged", + "securityContext": { + "runAsNonRoot": true, + "allowPrivilegeEscalation": true + }, + "env": [ + { + "name": "ENV_KEY", + "value": "ENV_VALUE" + }, + { + "name": "ENV_NEW_KEY", + "value": "ENV_NEW_VALUE" + } + ] + } + ] + } + } + } + }`) + + compareJSONAsMap(t, expectedResult, doc) +} From 73a3011c26f6faf0be7215bdf001aff3097e9754 Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Fri, 26 Jul 2019 18:55:27 -0700 Subject: [PATCH 8/8] modify policy in example to match more specific resource --- examples/demo/1_image_pull_policy/policy.yaml | 7 ------- examples/demo/6_qos/policy_qos.yaml | 6 ++++++ examples/demo/6_qos/qos.yaml | 2 ++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/demo/1_image_pull_policy/policy.yaml b/examples/demo/1_image_pull_policy/policy.yaml index 617ff192dd..01cf68179f 100644 --- a/examples/demo/1_image_pull_policy/policy.yaml +++ b/examples/demo/1_image_pull_policy/policy.yaml @@ -9,13 +9,6 @@ spec: resources: kinds: - Deployment - exclude: - resources: - name: nginx-deployment1 - selector : - matchLabels: - app: nginx1 - namespace: "default" mutate: overlay: spec: diff --git a/examples/demo/6_qos/policy_qos.yaml b/examples/demo/6_qos/policy_qos.yaml index 352772a8ea..96a55583e8 100644 --- a/examples/demo/6_qos/policy_qos.yaml +++ b/examples/demo/6_qos/policy_qos.yaml @@ -10,6 +10,9 @@ spec: resources: kinds: - Deployment + selector : + matchLabels: + test: qos mutate: overlay: spec: @@ -28,6 +31,9 @@ spec: resources: kinds: - Deployment + selector : + matchLabels: + test: qos validate: message: "Resource limits are required for CPU and memory" pattern: diff --git a/examples/demo/6_qos/qos.yaml b/examples/demo/6_qos/qos.yaml index d998bdfbc3..43abddf1e6 100644 --- a/examples/demo/6_qos/qos.yaml +++ b/examples/demo/6_qos/qos.yaml @@ -2,6 +2,8 @@ apiVersion: apps/v1 kind: Deployment metadata: name: qos-demo + labels: + test: qos spec: replicas: 1 selector: