mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
1723 lines
68 KiB
Go
1723 lines
68 KiB
Go
package engine
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"k8s.io/api/admission/v1beta1"
|
|
|
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
"github.com/nirmata/kyverno/pkg/engine/utils"
|
|
utils2 "github.com/nirmata/kyverno/pkg/utils"
|
|
"gotest.tools/assert"
|
|
)
|
|
|
|
func TestGetAnchorsFromMap_ThereAreAnchors(t *testing.T) {
|
|
rawMap := []byte(`{
|
|
"(name)":"nirmata-*",
|
|
"notAnchor1":123,
|
|
"(namespace)":"kube-?olicy",
|
|
"notAnchor2":"sample-text",
|
|
"object":{
|
|
"key1":"value1",
|
|
"(key2)":"value2"
|
|
}
|
|
}`)
|
|
|
|
var unmarshalled map[string]interface{}
|
|
err := json.Unmarshal(rawMap, &unmarshalled)
|
|
assert.NilError(t, err)
|
|
|
|
actualMap := utils.GetAnchorsFromMap(unmarshalled)
|
|
assert.Equal(t, len(actualMap), 2)
|
|
assert.Equal(t, actualMap["(name)"].(string), "nirmata-*")
|
|
assert.Equal(t, actualMap["(namespace)"].(string), "kube-?olicy")
|
|
}
|
|
|
|
func TestValidate_image_tag_fail(t *testing.T) {
|
|
// If image tag is latest then imagepull policy needs to be checked
|
|
rawPolicy := []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "validate-image"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "validate-tag",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "An image tag is required",
|
|
"pattern": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"image": "*:*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "validate-latest",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "imagePullPolicy 'Always' required with tag 'latest'",
|
|
"pattern": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"(image)": "*latest",
|
|
"imagePullPolicy": "NotPresent"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "myapp"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx",
|
|
"image": "nginx:latest",
|
|
"imagePullPolicy": "Always"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
msgs := []string{
|
|
"Validation rule 'validate-tag' succeeded.",
|
|
"Validation error: imagePullPolicy 'Always' required with tag 'latest'; Validation rule 'validate-latest' failed at path '/spec/containers/0/imagePullPolicy/'",
|
|
}
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, !er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_image_tag_pass(t *testing.T) {
|
|
// If image tag is latest then imagepull policy needs to be checked
|
|
rawPolicy := []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "validate-image"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "validate-tag",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "An image tag is required",
|
|
"pattern": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"image": "*:*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "validate-latest",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "imagePullPolicy 'Always' required with tag 'latest'",
|
|
"pattern": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"(image)": "*latest",
|
|
"imagePullPolicy": "Always"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "myapp"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx",
|
|
"image": "nginx:latest",
|
|
"imagePullPolicy": "Always"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
msgs := []string{
|
|
"Validation rule 'validate-tag' succeeded.",
|
|
"Validation rule 'validate-latest' succeeded.",
|
|
}
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_Fail_anyPattern(t *testing.T) {
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "validate-namespace"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "check-default-namespace",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "A namespace is required",
|
|
"anyPattern": [
|
|
{
|
|
"metadata": {
|
|
"namespace": "?*"
|
|
}
|
|
},
|
|
{
|
|
"metadata": {
|
|
"namespace": "!default"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "myapp"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx",
|
|
"image": "nginx"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"A namespace is required"}
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, !er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_host_network_port(t *testing.T) {
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "validate-host-network-port"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "validate-host-network-port",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "Host network and port are not allowed",
|
|
"pattern": {
|
|
"spec": {
|
|
"hostNetwork": false,
|
|
"containers": [
|
|
{
|
|
"name": "*",
|
|
"ports": [
|
|
{
|
|
"hostPort": null
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "nginx-host-network"
|
|
},
|
|
"spec": {
|
|
"hostNetwork": false,
|
|
"containers": [
|
|
{
|
|
"name": "nginx-host-network",
|
|
"image": "nginx",
|
|
"ports": [
|
|
{
|
|
"containerPort": 80,
|
|
"hostPort": 80
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation error: Host network and port are not allowed; Validation rule 'validate-host-network-port' failed at path '/spec/containers/0/ports/0/hostPort/'"}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, !er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_anchor_arraymap_pass(t *testing.T) {
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "validate-host-path"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "validate-host-path",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "Host path '/var/lib/' is not allowed",
|
|
"pattern": {
|
|
"spec": {
|
|
"volumes": [
|
|
{
|
|
"name": "*",
|
|
"=(hostPath)": {
|
|
"path": "!/var/lib"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "image-with-hostpath",
|
|
"labels": {
|
|
"app.type": "prod",
|
|
"namespace": "my-namespace"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "image-with-hostpath",
|
|
"image": "docker.io/nautiker/curl",
|
|
"volumeMounts": [
|
|
{
|
|
"name": "var-lib-etcd",
|
|
"mountPath": "/var/lib"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"volumes": [
|
|
{
|
|
"name": "var-lib-etcd",
|
|
"hostPath": {
|
|
"path": "/var/lib1"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
} `)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation rule 'validate-host-path' succeeded."}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_anchor_arraymap_fail(t *testing.T) {
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "validate-host-path"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "validate-host-path",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "Host path '/var/lib/' is not allowed",
|
|
"pattern": {
|
|
"spec": {
|
|
"volumes": [
|
|
{
|
|
"=(hostPath)": {
|
|
"path": "!/var/lib"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "image-with-hostpath",
|
|
"labels": {
|
|
"app.type": "prod",
|
|
"namespace": "my-namespace"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "image-with-hostpath",
|
|
"image": "docker.io/nautiker/curl",
|
|
"volumeMounts": [
|
|
{
|
|
"name": "var-lib-etcd",
|
|
"mountPath": "/var/lib"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"volumes": [
|
|
{
|
|
"name": "var-lib-etcd",
|
|
"hostPath": {
|
|
"path": "/var/lib"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
} `)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation error: Host path '/var/lib/' is not allowed; Validation rule 'validate-host-path' failed at path '/spec/volumes/0/hostPath/path/'"}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, !er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_anchor_map_notfound(t *testing.T) {
|
|
// anchor not present in resource
|
|
rawPolicy := []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-secaas-k8s"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pod rule 2",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "pod: validate run as non root user",
|
|
"pattern": {
|
|
"spec": {
|
|
"=(securityContext)": {
|
|
"runAsNonRoot": true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
} `)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "v1"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx",
|
|
"image": "nginx"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation rule 'pod rule 2' succeeded."}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_anchor_map_found_valid(t *testing.T) {
|
|
// anchor not present in resource
|
|
rawPolicy := []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-secaas-k8s"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pod rule 2",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "pod: validate run as non root user",
|
|
"pattern": {
|
|
"spec": {
|
|
"=(securityContext)": {
|
|
"runAsNonRoot": true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
} `)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "v1"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx",
|
|
"image": "nginx"
|
|
}
|
|
],
|
|
"securityContext": {
|
|
"runAsNonRoot": true
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation rule 'pod rule 2' succeeded."}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_anchor_map_found_invalid(t *testing.T) {
|
|
// anchor not present in resource
|
|
rawPolicy := []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-secaas-k8s"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pod rule 2",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "pod: validate run as non root user",
|
|
"pattern": {
|
|
"spec": {
|
|
"=(securityContext)": {
|
|
"runAsNonRoot": true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
} `)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "v1"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx",
|
|
"image": "nginx"
|
|
}
|
|
],
|
|
"securityContext": {
|
|
"runAsNonRoot": false
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation error: pod: validate run as non root user; Validation rule 'pod rule 2' failed at path '/spec/securityContext/runAsNonRoot/'"}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, !er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_AnchorList_pass(t *testing.T) {
|
|
// anchor not present in resource
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-secaas-k8s"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pod image rule",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"pattern": {
|
|
"spec": {
|
|
"=(containers)": [
|
|
{
|
|
"name": "nginx"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "v1"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx"
|
|
},
|
|
{
|
|
"name": "nginx"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation rule 'pod image rule' succeeded."}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
t.Log(r.Message)
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_AnchorList_fail(t *testing.T) {
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-secaas-k8s"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pod image rule",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"pattern": {
|
|
"spec": {
|
|
"=(containers)": [
|
|
{
|
|
"name": "nginx"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "v1"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx"
|
|
},
|
|
{
|
|
"name": "busy"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
// msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/1/name/' for resource Pod//myapp-pod."}
|
|
// for index, r := range er.PolicyResponse.Rules {
|
|
// // t.Log(r.Message)
|
|
// assert.Equal(t, r.Message, msgs[index])
|
|
// }
|
|
assert.Assert(t, !er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_existenceAnchor_fail(t *testing.T) {
|
|
// anchor not present in resource
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-secaas-k8s"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pod image rule",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"pattern": {
|
|
"spec": {
|
|
"^(containers)": [
|
|
{
|
|
"name": "nginx"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "v1"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "busy1"
|
|
},
|
|
{
|
|
"name": "busy"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
// msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/' for resource Pod//myapp-pod."}
|
|
|
|
// for index, r := range er.PolicyResponse.Rules {
|
|
// t.Log(r.Message)
|
|
// assert.Equal(t, r.Message, msgs[index])
|
|
// }
|
|
assert.Assert(t, !er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_existenceAnchor_pass(t *testing.T) {
|
|
// anchor not present in resource
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-secaas-k8s"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pod image rule",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"pattern": {
|
|
"spec": {
|
|
"^(containers)": [
|
|
{
|
|
"name": "nginx"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "myapp-pod",
|
|
"labels": {
|
|
"app": "v1"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "nginx"
|
|
},
|
|
{
|
|
"name": "busy"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation rule 'pod image rule' succeeded."}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_negationAnchor_deny(t *testing.T) {
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "validate-host-path"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "validate-host-path",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "Host path is not allowed",
|
|
"pattern": {
|
|
"spec": {
|
|
"volumes": [
|
|
{
|
|
"name": "*",
|
|
"X(hostPath)": null
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "image-with-hostpath",
|
|
"labels": {
|
|
"app.type": "prod",
|
|
"namespace": "my-namespace"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "image-with-hostpath",
|
|
"image": "docker.io/nautiker/curl",
|
|
"volumeMounts": [
|
|
{
|
|
"name": "var-lib-etcd",
|
|
"mountPath": "/var/lib"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"volumes": [
|
|
{
|
|
"name": "var-lib-etcd",
|
|
"hostPath": {
|
|
"path": "/var/lib1"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
} `)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation error: Host path is not allowed; Validation rule 'validate-host-path' failed at path '/spec/volumes/0/hostPath/'"}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, !er.IsSuccesful())
|
|
}
|
|
|
|
func TestValidate_negationAnchor_pass(t *testing.T) {
|
|
rawPolicy := []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "validate-host-path"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "validate-host-path",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "Host path is not allowed",
|
|
"pattern": {
|
|
"spec": {
|
|
"volumes": [
|
|
{
|
|
"name": "*",
|
|
"X(hostPath)": null
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
rawResource := []byte(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "image-with-hostpath",
|
|
"labels": {
|
|
"app.type": "prod",
|
|
"namespace": "my-namespace"
|
|
}
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "image-with-hostpath",
|
|
"image": "docker.io/nautiker/curl",
|
|
"volumeMounts": [
|
|
{
|
|
"name": "var-lib-etcd",
|
|
"mountPath": "/var/lib"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"volumes": [
|
|
{
|
|
"name": "var-lib-etcd",
|
|
"emptyDir": {}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
err := json.Unmarshal(rawPolicy, &policy)
|
|
assert.NilError(t, err)
|
|
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
|
assert.NilError(t, err)
|
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
|
msgs := []string{"Validation rule 'validate-host-path' succeeded."}
|
|
|
|
for index, r := range er.PolicyResponse.Rules {
|
|
assert.Equal(t, r.Message, msgs[index])
|
|
}
|
|
assert.Assert(t, er.IsSuccesful())
|
|
}
|
|
|
|
func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
|
|
resourceRaw := []byte(`{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "check-root-user"
|
|
},
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "check-root-user-a",
|
|
"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"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"pattern": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"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,
|
|
Context: ctx,
|
|
NewResource: *resourceUnstructured}
|
|
er := Validate(policyContext)
|
|
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
|
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation error: ; Validation rule 'test-path-not-exist' failed. 'could not find variable request.object.metadata.name1 at path /spec/containers/0/name'")
|
|
}
|
|
|
|
func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfies(t *testing.T) {
|
|
resourceRaw := []byte(`{
|
|
"apiVersion": "v1",
|
|
"kind": "Deployment",
|
|
"metadata": {
|
|
"name": "test"
|
|
},
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "test-pod",
|
|
"image": "nginxinc/nginx-unprivileged"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
policyraw := []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "substitute-variable"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "test-path-not-exist",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Deployment"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"anyPattern": [
|
|
{
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "{{request.object.metadata.name1}}*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "{{request.object.metadata.name}}*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
assert.NilError(t, json.Unmarshal(policyraw, &policy))
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext()
|
|
err = ctx.AddResource(resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
policyContext := PolicyContext{
|
|
Policy: policy,
|
|
Context: ctx,
|
|
NewResource: *resourceUnstructured}
|
|
er := Validate(policyContext)
|
|
assert.Assert(t, er.PolicyResponse.Rules[0].Success)
|
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation rule 'test-path-not-exist' anyPattern[1] succeeded.")
|
|
}
|
|
|
|
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *testing.T) {
|
|
resourceRaw := []byte(`{
|
|
"apiVersion": "v1",
|
|
"kind": "Deployment",
|
|
"metadata": {
|
|
"name": "test"
|
|
},
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "test-pod",
|
|
"image": "nginxinc/nginx-unprivileged"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
policyraw := []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "substitute-variable"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "test-path-not-exist",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Deployment"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"anyPattern": [
|
|
{
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "{{request.object.metadata.name1}}*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "{{request.object.metadata.name2}}*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
assert.NilError(t, json.Unmarshal(policyraw, &policy))
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext()
|
|
err = ctx.AddResource(resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
policyContext := PolicyContext{
|
|
Policy: policy,
|
|
Context: ctx,
|
|
NewResource: *resourceUnstructured}
|
|
er := Validate(policyContext)
|
|
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
|
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Substitutions failed: [could not find variable request.object.metadata.name1 at path /spec/template/spec/containers/0/name could not find variable request.object.metadata.name2 at path /spec/template/spec/containers/0/name]")
|
|
}
|
|
|
|
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) {
|
|
resourceRaw := []byte(`{
|
|
"apiVersion": "v1",
|
|
"kind": "Deployment",
|
|
"metadata": {
|
|
"name": "test"
|
|
},
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "pod-test-pod",
|
|
"image": "nginxinc/nginx-unprivileged"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
policyraw := []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "substitute-variable"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "test-path-not-exist",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Deployment"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"anyPattern": [
|
|
{
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "{{request.object.metadata.name}}*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "{{request.object.metadata.name}}*"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
var policy kyverno.ClusterPolicy
|
|
assert.NilError(t, json.Unmarshal(policyraw, &policy))
|
|
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext()
|
|
err = ctx.AddResource(resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
policyContext := PolicyContext{
|
|
Policy: policy,
|
|
Context: ctx,
|
|
NewResource: *resourceUnstructured}
|
|
er := Validate(policyContext)
|
|
|
|
// expectedMsg := "Validation error: ; Validation rule test-path-not-exist anyPattern[0] failed at path /spec/template/spec/containers/0/name/. Validation rule test-path-not-exist anyPattern[1] failed at path /spec/template/spec/containers/0/name/."
|
|
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
|
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation rule 'test-path-not-exist' has failed")
|
|
}
|
|
|
|
func Test_denyFeatureIssue744(t *testing.T) {
|
|
testcases := []struct {
|
|
description string
|
|
policy []byte
|
|
request []byte
|
|
userInfo []byte
|
|
requestDenied bool
|
|
}{
|
|
{
|
|
description: "Blocks delete requests for resources with label allow-deletes(success case)",
|
|
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"block-deletes-success"},"spec":{"validationFailureAction":"enforce","background":false,"rules":[{"name":"check-allow-deletes","match":{"resources":{"selector":{"matchLabels":{"allow-deletes":"false"}}}},"exclude":{"clusterRoles":["random"]},"validate":{"message":"Deleting {{request.oldObject.kind}} / {{request.oldObject.metadata.name}} is not allowed","deny":{"conditions":[{"key":"{{request.operation}}","operator":"Equal","value":"DELETE"}]}}}]}}`),
|
|
request: []byte(`{"uid":"b553344a-172a-4257-8ec4-a8f379f8b844","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"name":"hello-world","namespace":"default","operation":"DELETE","userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]},"object":null,"oldObject":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"f093e3da-f13a-474f-87e8-43e98fe363bf","resourceVersion":"1983","creationTimestamp":"2020-05-06T20:28:43Z","labels":{"allow-deletes":"false"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-deletes\":\"false\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","nodeName":"kind-control-plane","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","conditions":[{"type":"Initialized","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:28:43Z"},{"type":"Ready","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:28:43Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"ContainersReady","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:28:43Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"PodScheduled","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:28:43Z"}],"hostIP":"172.17.0.2","startTime":"2020-05-06T20:28:43Z","containerStatuses":[{"name":"hello-world","state":{"waiting":{"reason":"ContainerCreating"}},"lastState":{},"ready":false,"restartCount":0,"image":"hello-world:latest","imageID":"","started":false}],"qosClass":"Burstable"}},"dryRun":false,"options":{"kind":"DeleteOptions","apiVersion":"meta.k8s.io/v1","gracePeriodSeconds":30,"propagationPolicy":"Background"}}`),
|
|
userInfo: []byte(`{"roles":null,"clusterRoles":["cluster-admin","system:basic-user","system:discovery","system:public-info-viewer"],"userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]}}`),
|
|
requestDenied: true,
|
|
},
|
|
{
|
|
description: "Blocks delete requests for resources with label allow-deletes(failure case)",
|
|
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"block-deletes-failure"},"spec":{"validationFailureAction":"enforce","background":false,"rules":[{"name":"check-allow-deletes","match":{"resources":{"selector":{"matchLabels":{"allow-deletes":"false"}}}},"exclude":{"clusterRoles":["random"]},"validate":{"message":"Deleting {{request.oldObject.kind}} / {{request.oldObject.metadata.name}} is not allowed","deny":{"conditions":[{"key":"{{request.operation}}","operator":"Equal","value":"DELETE"}]}}}]}}`),
|
|
request: []byte(`{"uid":"9a83234d-95d1-4105-b6bf-7d72fd0183ce","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"subResource":"status","requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"requestSubResource":"status","name":"hello-world","namespace":"default","operation":"UPDATE","userInfo":{"username":"system:node:kind-control-plane","groups":["system:nodes","system:authenticated"]},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"10fb7e1f-3710-43fa-9b7d-fc532b5ff70e","resourceVersion":"2829","creationTimestamp":"2020-05-06T20:36:51Z","labels":{"allow-deletes":"false"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-deletes\":\"false\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","nodeName":"kind-control-plane","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","conditions":[{"type":"Initialized","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:36:51Z"},{"type":"Ready","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:36:51Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"ContainersReady","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:36:51Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"PodScheduled","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:36:51Z"}],"hostIP":"172.17.0.2","startTime":"2020-05-06T20:36:51Z","containerStatuses":[{"name":"hello-world","state":{"waiting":{"reason":"ContainerCreating"}},"lastState":{},"ready":false,"restartCount":0,"image":"hello-world:latest","imageID":"","started":false}],"qosClass":"Burstable"}},"oldObject":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"10fb7e1f-3710-43fa-9b7d-fc532b5ff70e","resourceVersion":"2829","creationTimestamp":"2020-05-06T20:36:51Z","labels":{"allow-deletes":"false"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-deletes\":\"false\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","nodeName":"kind-control-plane","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","conditions":[{"type":"PodScheduled","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:36:51Z"}],"qosClass":"Burstable"}},"dryRun":false,"options":{"kind":"UpdateOptions","apiVersion":"meta.k8s.io/v1"}}`),
|
|
userInfo: []byte(`{"roles":["kube-system:kubeadm:nodes-kubeadm-config","kube-system:kubeadm:kubelet-config-1.17"],"clusterRoles":["system:discovery","system:certificates.k8s.io:certificatesigningrequests:selfnodeclient","system:public-info-viewer","system:basic-user"],"userInfo":{"username":"kubernetes-admin","groups":["system:nodes","system:authenticated"]}}`),
|
|
requestDenied: false,
|
|
},
|
|
{
|
|
description: "Blocks update requests for resources with label allow-updates(success case)",
|
|
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"block-updates-success"},"spec":{"validationFailureAction":"enforce","background":false,"rules":[{"name":"check-allow-updates","match":{"resources":{"selector":{"matchLabels":{"allow-updates":"false"}}}},"exclude":{"clusterRoles":["random"]},"validate":{"message":"Updating {{request.object.kind}} / {{request.object.metadata.name}} is not allowed","deny":{"conditions":[{"key":"{{request.operation}}","operator":"Equals","value":"UPDATE"}]}}}]}}`),
|
|
request: []byte(`{"uid":"7b0600b7-0258-4ecb-9666-c2839bd19612","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"subResource":"status","requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"requestSubResource":"status","name":"hello-world","namespace":"default","operation":"UPDATE","userInfo":{"username":"system:node:kind-control-plane","groups":["system:authenticated"]},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"2b42971e-6fcf-41a7-ae44-80963f957eae","resourceVersion":"3438","creationTimestamp":"2020-05-06T20:41:37Z","labels":{"allow-updates":"false","something":"hereeeeeseee"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-updates\":\"false\",\"something\":\"hereeeeeseee\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","nodeName":"kind-control-plane","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Running","conditions":[{"type":"Initialized","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:41:37Z"},{"type":"Ready","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:41:37Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"ContainersReady","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:41:37Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"PodScheduled","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:41:37Z"}],"hostIP":"172.17.0.2","podIP":"10.244.0.8","podIPs":[{"ip":"10.244.0.8"}],"startTime":"2020-05-06T20:41:37Z","containerStatuses":[{"name":"hello-world","state":{"terminated":{"exitCode":0,"reason":"Completed","startedAt":"2020-05-06T20:42:01Z","finishedAt":"2020-05-06T20:42:01Z","containerID":"containerd://46dc1c3dead976b5cc6e5f6a8dc86988e8ce401e6fd903d4637848dd4baac0c4"}},"lastState":{},"ready":false,"restartCount":0,"image":"docker.io/library/hello-world:latest","imageID":"docker.io/library/hello-world@sha256:8e3114318a995a1ee497790535e7b88365222a21771ae7e53687ad76563e8e76","containerID":"containerd://46dc1c3dead976b5cc6e5f6a8dc86988e8ce401e6fd903d4637848dd4baac0c4","started":false}],"qosClass":"Burstable"}},"oldObject":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"2b42971e-6fcf-41a7-ae44-80963f957eae","resourceVersion":"3438","creationTimestamp":"2020-05-06T20:41:37Z","labels":{"allow-updates":"false","something":"hereeeeeseee"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-updates\":\"false\",\"something\":\"hereeeeeseee\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","nodeName":"kind-control-plane","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","conditions":[{"type":"Initialized","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:41:37Z"},{"type":"Ready","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:41:37Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"ContainersReady","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:41:37Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"PodScheduled","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:41:37Z"}],"hostIP":"172.17.0.2","startTime":"2020-05-06T20:41:37Z","containerStatuses":[{"name":"hello-world","state":{"waiting":{"reason":"ContainerCreating"}},"lastState":{},"ready":false,"restartCount":0,"image":"hello-world:latest","imageID":"","started":false}],"qosClass":"Burstable"}},"dryRun":false,"options":{"kind":"UpdateOptions","apiVersion":"meta.k8s.io/v1"}}`),
|
|
userInfo: []byte(`{"roles":["kube-system:kubeadm:kubelet-config-1.17","kube-system:kubeadm:nodes-kubeadm-config"],"clusterRoles":["system:basic-user","system:certificates.k8s.io:certificatesigningrequests:selfnodeclient","system:public-info-viewer","system:discovery"],"userInfo":{"username":"kubernetes-admin","groups":["system:authenticated"]}}`),
|
|
requestDenied: true,
|
|
},
|
|
{
|
|
description: "Blocks update requests for resources with label allow-updates(failure case)",
|
|
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"block-updates-failure"},"spec":{"validationFailureAction":"enforce","background":false,"rules":[{"name":"check-allow-deletes","match":{"resources":{"selector":{"matchLabels":{"allow-deletes":"false"}}}},"exclude":{"clusterRoles":["random"]},"validate":{"message":"Deleting {{request.oldObject.kind}} / {{request.oldObject.metadata.name}} is not allowed","deny":{"conditions":[{"key":"{{request.operation}}","operator":"Equal","value":"DELETE"}]}}}]}}`),
|
|
request: []byte(`{"uid":"9c284cdb-b0de-42aa-adf5-649a44bc861b","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"name":"hello-world","namespace":"default","operation":"CREATE","userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"41a928a7-73f4-419f-bd64-de11f4f0a8ca","creationTimestamp":"2020-05-06T20:43:50Z","labels":{"allow-updates":"false","something":"hereeeeeseee"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-updates\":\"false\",\"something\":\"hereeeeeseee\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj"}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","qosClass":"Burstable"}},"oldObject":null,"dryRun":false,"options":{"kind":"CreateOptions","apiVersion":"meta.k8s.io/v1"}}`),
|
|
userInfo: []byte(`{"roles":null,"clusterRoles":["system:public-info-viewer","cluster-admin","system:discovery","system:basic-user"],"userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]}}`),
|
|
requestDenied: false,
|
|
},
|
|
{
|
|
description: "Blocks certain fields(success case)",
|
|
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"prevent-field-update-success"},"spec":{"validationFailureAction":"enforce","background":false,"rules":[{"name":"prevent-field-update","match":{"resources":{"selector":{"matchLabels":{"allow-updates":"false"}}}},"validate":{"message":"Updating field label 'something' is not allowed","deny":{"conditions":[{"key":"{{request.object.metadata.labels.something}}","operator":"NotEqual","value":""},{"key":"{{request.object.metadata.labels.something}}","operator":"NotEquals","value":"{{request.oldObject.metadata.labels.something}}"}]}}}]}}`),
|
|
request: []byte(`{"uid":"11d46f83-a31b-444e-8209-c43b24f1af8a","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"name":"hello-world","namespace":"default","operation":"UPDATE","userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"42bd0f0a-4b1f-4f7c-a40d-4dbed5522732","resourceVersion":"4333","creationTimestamp":"2020-05-06T20:51:58Z","labels":{"allow-updates":"false","something":"existes","something2":"feeereeeeeeeee"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-updates\":\"false\",\"something\":\"existes\",\"something2\":\"feeereeeeeeeee\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","qosClass":"Burstable"}},"oldObject":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"42bd0f0a-4b1f-4f7c-a40d-4dbed5522732","resourceVersion":"4333","creationTimestamp":"2020-05-06T20:51:58Z","labels":{"allow-updates":"false","something":"exists","something2":"feeereeeeeeeee"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-updates\":\"false\",\"something\":\"exists\",\"something2\":\"feeereeeeeeeee\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","qosClass":"Burstable"}},"dryRun":false,"options":{"kind":"UpdateOptions","apiVersion":"meta.k8s.io/v1"}}`),
|
|
userInfo: []byte(`{"roles":null,"clusterRoles":null,"userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]}}`),
|
|
requestDenied: true,
|
|
},
|
|
{
|
|
description: "Blocks certain fields(failure case)",
|
|
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"prevent-field-update-failure"},"spec":{"validationFailureAction":"enforce","background":false,"rules":[{"name":"prevent-field-update","match":{"resources":{"selector":{"matchLabels":{"allow-updates":"false"}}}},"validate":{"message":"Updating field label 'something' is not allowed","deny":{"conditions":[{"key":"{{request.object.metadata.labels.something}}","operator":"NotEqual","value":""},{"key":"{{request.object.metadata.labels.something}}","operator":"NotEquals","value":"{{request.oldObject.metadata.labels.something}}"}]}}}]}}`),
|
|
request: []byte(`{"uid":"cbdce9bb-741d-466a-a440-36155eb4b45b","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"name":"hello-world","namespace":"kube-system","operation":"CREATE","userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"kube-system","uid":"490c240c-f96a-4d5a-8860-75597bab0a7e","creationTimestamp":"2020-05-06T21:01:50Z","annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"hello-world\",\"namespace\":\"kube-system\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-8h2h8","secret":{"secretName":"default-token-8h2h8"}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-8h2h8","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","qosClass":"Burstable"}},"oldObject":null,"dryRun":false,"options":{"kind":"CreateOptions","apiVersion":"meta.k8s.io/v1"}}`),
|
|
userInfo: []byte(`{"roles":null,"clusterRoles":null,"userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]}}`),
|
|
requestDenied: false,
|
|
},
|
|
{
|
|
description: "Deny all requests on a namespace",
|
|
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"block-request"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"block-request","match":{"resources":{"namespaces":["kube-system"]}},"validate":{"deny":{}}}]}}`),
|
|
request: []byte(`{"uid":"2cf2b192-2c25-4f14-ac3a-315408d398f2","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"name":"hello-world","namespace":"default","operation":"UPDATE","userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"f5c33eaf-79d8-4bc0-8819-749b3606012c","resourceVersion":"5470","creationTimestamp":"2020-05-06T20:57:15Z","labels":{"allow-updates":"false","something":"existes","something2":"feeereeeeeeeee"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-updates\":\"false\",\"something\":\"existes\",\"something2\":\"feeereeeeeeeee\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","nodeName":"kind-control-plane","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","conditions":[{"type":"Initialized","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:57:15Z"},{"type":"Ready","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:57:15Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"ContainersReady","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:57:15Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"PodScheduled","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:57:15Z"}],"hostIP":"172.17.0.2","startTime":"2020-05-06T20:57:15Z","containerStatuses":[{"name":"hello-world","state":{"waiting":{"reason":"ContainerCreating"}},"lastState":{},"ready":false,"restartCount":0,"image":"hello-world:latest","imageID":"","started":false}],"qosClass":"Burstable"}},"oldObject":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"hello-world","namespace":"default","uid":"f5c33eaf-79d8-4bc0-8819-749b3606012c","resourceVersion":"5470","creationTimestamp":"2020-05-06T20:57:15Z","labels":{"allow-updates":"false","something":"existes","something2":"feeereeeeeeeee"},"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"allow-updates\":\"false\",\"something\":\"existes\",\"something2\":\"feeereeeeeeeee\"},\"name\":\"hello-world\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"hello-world:latest\",\"name\":\"hello-world\",\"ports\":[{\"containerPort\":80}],\"resources\":{\"limits\":{\"cpu\":\"0.2\",\"memory\":\"30Mi\"},\"requests\":{\"cpu\":\"0.1\",\"memory\":\"20Mi\"}}}]}}\n"}},"spec":{"volumes":[{"name":"default-token-4q2mj","secret":{"secretName":"default-token-4q2mj","defaultMode":420}}],"containers":[{"name":"hello-world","image":"hello-world:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"200m","memory":"30Mi"},"requests":{"cpu":"100m","memory":"20Mi"}},"volumeMounts":[{"name":"default-token-4q2mj","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","nodeName":"kind-control-plane","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true},"status":{"phase":"Pending","conditions":[{"type":"Initialized","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:57:15Z"},{"type":"Ready","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:57:15Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"ContainersReady","status":"False","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:57:15Z","reason":"ContainersNotReady","message":"containers with unready status: [hello-world]"},{"type":"PodScheduled","status":"True","lastProbeTime":null,"lastTransitionTime":"2020-05-06T20:57:15Z"}],"hostIP":"172.17.0.2","startTime":"2020-05-06T20:57:15Z","containerStatuses":[{"name":"hello-world","state":{"waiting":{"reason":"ContainerCreating"}},"lastState":{},"ready":false,"restartCount":0,"image":"hello-world:latest","imageID":"","started":false}],"qosClass":"Burstable"}},"dryRun":false,"options":{"kind":"UpdateOptions","apiVersion":"meta.k8s.io/v1"}}`),
|
|
userInfo: []byte(`{"roles":null,"clusterRoles":null,"userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]}}`),
|
|
requestDenied: false,
|
|
},
|
|
}
|
|
|
|
var err error
|
|
for _, testcase := range testcases {
|
|
var policy kyverno.ClusterPolicy
|
|
err = json.Unmarshal(testcase.policy, &policy)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var request *v1beta1.AdmissionRequest
|
|
err = json.Unmarshal(testcase.request, &request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var userInfo kyverno.RequestInfo
|
|
err = json.Unmarshal(testcase.userInfo, &userInfo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx := context.NewContext()
|
|
err = ctx.AddRequest(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = ctx.AddUserInfo(userInfo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = ctx.AddSA(userInfo.AdmissionUserInfo.Username)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
newR, oldR, err := utils2.ExtractResources(nil, request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
pc := PolicyContext{
|
|
Policy: policy,
|
|
NewResource: newR,
|
|
OldResource: oldR,
|
|
AdmissionInfo: userInfo,
|
|
Context: ctx,
|
|
}
|
|
resp := Validate(pc)
|
|
if resp.IsSuccesful() == !testcase.requestDenied {
|
|
continue
|
|
}
|
|
|
|
t.Errorf("Testcase has failed, policy: %v", policy.Name)
|
|
}
|
|
}
|