1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00
kyverno/pkg/engine/mutation_test.go
Vyankatesh Kudtarkar e22e9499b6
CLI fix for foreach policies (#2997)
* CLI fix for foreach policies

* add test-case for foreach container and initcontainer

* fix comments

Co-authored-by: Jim Bugwadia <jim@nirmata.com>
2022-01-18 23:38:49 +00:00

911 lines
22 KiB
Go

package engine
import (
"encoding/json"
"reflect"
"strings"
"testing"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/kyverno/store"
"gotest.tools/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func Test_VariableSubstitutionPatchStrategicMerge(t *testing.T) {
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "add-label"
},
"spec": {
"rules": [
{
"name": "add-name-label",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"metadata": {
"labels": {
"appname": "{{request.object.metadata.name}}"
}
}
}
}
}
]
}
}`)
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "check-root-user"
},
"spec": {
"containers": [
{
"name": "check-root-user",
"image": "nginxinc/nginx-unprivileged",
"securityContext": {
"runAsNonRoot": true
}
}
]
}
}`)
expectedPatch := []byte(`{"op":"add","path":"/metadata/labels","value":{"appname":"check-root-user"}}`)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
if err != nil {
t.Error(err)
}
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
if err != nil {
t.Error(err)
}
value, err := ctx.Query("request.object.metadata.name")
t.Log(value)
if err != nil {
t.Error(err)
}
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Mutate(policyContext)
t.Log(string(expectedPatch))
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
t.Log(string(er.PolicyResponse.Rules[0].Patches[0]))
if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) {
t.Error("patches dont match")
}
}
func Test_variableSubstitutionPathNotExist(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "check-root-user"
},
"spec": {
"containers": [
{
"name": "check-root-user",
"image": "nginxinc/nginx-unprivileged",
"securityContext": {
"runAsNonRoot": true
}
}
]
}
}`)
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "substitute-variable"
},
"spec": {
"rules": [
{
"name": "test-path-not-exist",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"spec": {
"name": "{{request.object.metadata.name1}}"
}
}
}
}
]
}
}`)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
assert.NilError(t, err)
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Mutate(policyContext)
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "Unknown key \"name1\" in path"))
}
func Test_variableSubstitutionCLI(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "nginx-config-test"
},
"spec": {
"containers": [
{
"image": "nginx:latest",
"name": "test-nginx"
}
]
}
}`)
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "cm-variable-example"
},
"spec": {
"rules": [
{
"name": "example-configmap-lookup",
"context": [
{
"name": "dictionary",
"configMap": {
"name": "mycmap",
"namespace": "default"
}
}
],
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"metadata": {
"labels": {
"my-environment-name": "{{dictionary.data.env}}"
}
}
}
}
}
]
}
}`)
configMapVariableContext := store.Context{
Policies: []store.Policy{
{
Name: "cm-variable-example",
Rules: []store.Rule{
{
Name: "example-configmap-lookup",
Values: map[string]string{
"dictionary.data.env": "dev1",
},
},
},
},
},
}
expectedPatch := []byte(`{"op":"add","path":"/metadata/labels","value":{"my-environment-name":"dev1"}}`)
store.SetContext(configMapVariableContext)
store.SetMock(true)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
assert.NilError(t, err)
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resourceUnstructured,
}
er := Mutate(policyContext)
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
t.Log(string(expectedPatch))
t.Log(string(er.PolicyResponse.Rules[0].Patches[0]))
if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) {
t.Error("patches don't match")
}
}
// https://github.com/kyverno/kyverno/issues/2022
func Test_chained_rules(t *testing.T) {
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "replace-image-registry",
"annotations": {
"policies.kyverno.io/minversion": "1.4.2"
}
},
"spec": {
"background": false,
"rules": [
{
"name": "replace-image-registry",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"spec": {
"containers": [
{
"(name)": "*",
"image": "{{regex_replace_all('^[^/]+','{{@}}','myregistry.corp.com')}}"
}
]
}
}
}
},
{
"name": "replace-image-registry-chained",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"spec": {
"containers": [
{
"(name)": "*",
"image": "{{regex_replace_all('\\b(myregistry.corp.com)\\b','{{@}}','otherregistry.corp.com')}}"
}
]
}
}
}
}
]
}
}`)
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test"
},
"spec": {
"containers": [
{
"name": "test",
"image": "foo/bash:5.0"
}
]
}
}`)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
assert.NilError(t, err)
resource, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResourceAsObject(resource.Object)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resource,
}
err = ctx.AddImageInfo(resource)
assert.NilError(t, err)
err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
assert.NilError(t, err)
er := Mutate(policyContext)
containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
assert.NilError(t, err)
assert.Equal(t, containers[0].(map[string]interface{})["image"], "otherregistry.corp.com/foo/bash:5.0")
assert.Equal(t, len(er.PolicyResponse.Rules), 2)
assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
assert.Equal(t, len(er.PolicyResponse.Rules[1].Patches), 1)
assert.Equal(t, string(er.PolicyResponse.Rules[0].Patches[0]), `{"op":"replace","path":"/spec/containers/0/image","value":"myregistry.corp.com/foo/bash:5.0"}`)
assert.Equal(t, string(er.PolicyResponse.Rules[1].Patches[0]), `{"op":"replace","path":"/spec/containers/0/image","value":"otherregistry.corp.com/foo/bash:5.0"}`)
}
func Test_precondition(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "nginx-config-test",
"labels": {
"app.kubernetes.io/managed-by": "Helm"
}
},
"spec": {
"containers": [
{
"image": "nginx:latest",
"name": "test-nginx"
}
]
}
}`)
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "cm-variable-example"
},
"spec": {
"rules": [
{
"name": "example-configmap-lookup",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"preconditions": [
{
"key": "{{ request.object.metadata.labels.\"app.kubernetes.io/managed-by\"}}",
"operator": "Equals",
"value": "Helm"
}
],
"mutate": {
"patchStrategicMerge": {
"metadata": {
"labels": {
"my-added-label": "test"
}
}
}
}
}
]
}
}`)
expectedPatch := []byte(`{"op":"add","path":"/metadata/labels/my-added-label","value":"test"}`)
store.SetMock(true)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
assert.NilError(t, err)
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resourceUnstructured,
}
er := Mutate(policyContext)
t.Log(string(expectedPatch))
t.Log(string(er.PolicyResponse.Rules[0].Patches[0]))
if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) {
t.Error("patches don't match")
}
}
func Test_nonZeroIndexNumberPatchesJson6902(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Endpoints",
"metadata": {
"name": "my-service"
},
"subsets": [
{
"addresses": [
{
"ip": "127.0.0.1"
}
]
}
]
}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "policy-endpoints"
},
"spec": {
"rules": [
{
"name": "Add IP to subset",
"match": {
"resources": {
"kinds": [
"Endpoints"
]
}
},
"preconditions": [
{
"key": "{{ request.object.subsets[] | length(@) }}",
"operator": "Equals",
"value": "1"
}
],
"mutate": {
"patchesJson6902": "- path: \"/subsets/0/addresses/-\"\n op: add\n value: {\"ip\":\"192.168.42.172\"}"
}
},
{
"name": "Add IP to subsets",
"match": {
"resources": {
"kinds": [
"Endpoints"
]
}
},
"preconditions": [
{
"key": "{{ request.object.subsets[] | length(@) }}",
"operator": "Equals",
"value": "2"
}
],
"mutate": {
"patchesJson6902": "- path: \"/subsets/0/addresses/-\"\n op: add\n value: {\"ip\":\"192.168.42.172\"}\n- path: \"/subsets/1/addresses/-\"\n op: add\n value: {\"ip\":\"192.168.42.173\"}"
}
}
]
}
}`)
expectedPatch := []byte(`{"op":"add","path":"/subsets/0/addresses/1","value":{"ip":"192.168.42.172"}}`)
store.SetMock(true)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyraw, &policy)
assert.NilError(t, err)
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resourceUnstructured,
}
er := Mutate(policyContext)
t.Log(string(expectedPatch))
t.Log(string(er.PolicyResponse.Rules[0].Patches[0]))
if !reflect.DeepEqual(expectedPatch, er.PolicyResponse.Rules[0].Patches[0]) {
t.Error("patches don't match")
}
}
func Test_foreach(t *testing.T) {
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "replace-image-registry"
},
"spec": {
"background": false,
"rules": [
{
"name": "replace-image-registry",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"foreach": [
{
"list": "request.object.spec.containers",
"patchStrategicMerge": {
"spec": {
"containers": [
{
"name": "{{ element.name }}",
"image": "registry.io/{{images.containers.{{element.name}}.path}}:{{images.containers.{{element.name}}.tag}}"
}
]
}
}
}
]
}
}
]
}
}`)
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test"
},
"spec": {
"containers": [
{
"name": "test1",
"image": "foo1/bash1:5.0"
},
{
"name": "test2",
"image": "foo2/bash2:5.0"
},
{
"name": "test3",
"image": "foo3/bash3:5.0"
}
]
}
}`)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
assert.NilError(t, err)
resource, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResourceAsObject(resource.Object)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resource,
}
err = ctx.AddImageInfo(resource)
assert.NilError(t, err)
err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
assert.NilError(t, err)
er := Mutate(policyContext)
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusPass)
containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
assert.NilError(t, err)
for _, c := range containers {
ctnr := c.(map[string]interface{})
switch ctnr["name"] {
case "test1":
assert.Equal(t, ctnr["image"], "registry.io/foo1/bash1:5.0")
case "test2":
assert.Equal(t, ctnr["image"], "registry.io/foo2/bash2:5.0")
case "test3":
assert.Equal(t, ctnr["image"], "registry.io/foo3/bash3:5.0")
}
}
}
func Test_foreach_element_mutation(t *testing.T) {
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "mutate-privileged"
},
"spec": {
"validationFailureAction": "audit",
"background": false,
"webhookTimeoutSeconds": 10,
"failurePolicy": "Fail",
"rules": [
{
"name": "set-privileged",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"foreach": [
{
"list": "request.object.spec.containers",
"patchStrategicMerge": {
"spec": {
"containers": [
{
"(name)": "{{ element.name }}",
"securityContext": {
"privileged": false
}
}
]
}
}
}
]
}
}
]
}
}`)
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "nginx"
},
"spec": {
"containers": [
{
"name": "nginx1",
"image": "nginx"
},
{
"name": "nginx2",
"image": "nginx"
}
]
}
}`)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
assert.NilError(t, err)
resource, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResourceAsObject(resource.Object)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resource,
}
err = ctx.AddImageInfo(resource)
assert.NilError(t, err)
err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
assert.NilError(t, err)
er := Mutate(policyContext)
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusPass)
containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
assert.NilError(t, err)
for _, c := range containers {
ctnr := c.(map[string]interface{})
_securityContext, ok := ctnr["securityContext"]
assert.Assert(t, ok)
securityContext := _securityContext.(map[string]interface{})
assert.Equal(t, securityContext["privileged"], false)
}
}
func Test_Container_InitContainer_foreach(t *testing.T) {
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "prepend-registry",
"annotations": {
"pod-policies.kyverno.io/autogen-controllers": "none"
}
},
"spec": {
"background": false,
"rules": [
{
"name": "prepend-registry-containers",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"foreach": [
{
"list": "request.object.spec.containers",
"patchStrategicMerge": {
"spec": {
"containers": [
{
"name": "{{ element.name }}",
"image": "registry.io/{{ images.containers.\"{{element.name}}\".path}}:{{images.containers.\"{{element.name}}\".tag}}"
}
]
}
}
},
{
"list": "request.object.spec.initContainers",
"patchStrategicMerge": {
"spec": {
"initContainers": [
{
"name": "{{ element.name }}",
"image": "registry.io/{{ images.initContainers.\"{{element.name}}\".name}}:{{images.initContainers.\"{{element.name}}\".tag}}"
}
]
}
}
}
]
}
}
]
}
}`)
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "mypod"
},
"spec": {
"automountServiceAccountToken": false,
"initContainers": [
{
"name": "alpine",
"image": "alpine:latest"
},
{
"name": "busybox",
"image": "busybox:1.28"
}
],
"containers": [
{
"name": "nginx",
"image": "nginx:1.2.3"
},
{
"name": "redis",
"image": "redis:latest"
}
]
}
}`)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
assert.NilError(t, err)
resource, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResourceAsObject(resource.Object)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resource,
}
err = ctx.AddImageInfo(resource)
assert.NilError(t, err)
err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
assert.NilError(t, err)
er := Mutate(policyContext)
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusPass)
containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
assert.NilError(t, err)
for _, c := range containers {
ctnr := c.(map[string]interface{})
switch ctnr["name"] {
case "alpine":
assert.Equal(t, ctnr["image"], "registry.io/alpine:latest")
case "busybox":
assert.Equal(t, ctnr["image"], "registry.io/busybox:1.28")
}
}
initContainers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "initContainers")
assert.NilError(t, err)
for _, c := range initContainers {
ctnr := c.(map[string]interface{})
switch ctnr["name"] {
case "nginx":
assert.Equal(t, ctnr["image"], "registry.io/nginx:1.2.3")
case "redis":
assert.Equal(t, ctnr["image"], "registry.io/redis:latest")
}
}
}