1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00
kyverno/pkg/engine/utils_test.go
2020-02-09 19:56:38 +05:30

599 lines
16 KiB
Go

package engine
import (
"encoding/json"
"fmt"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
context "github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/utils"
"gotest.tools/assert"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestMatchesResourceDescription(t *testing.T) {
tcs := []struct {
Description string
AdmissionInfo kyverno.RequestInfo
Resource []byte
Policy []byte
areErrorsExpected bool
}{
{
Description: "Should match pod and not exclude it",
AdmissionInfo: kyverno.RequestInfo{
ClusterRoles: []string{"admin"},
},
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterroles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: false,
},
{
Description: "Should exclude resource since it matches the exclude block",
AdmissionInfo: kyverno.RequestInfo{
ClusterRoles: []string{"system:node"},
},
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterroles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: true,
},
{
Description: "Should not fail if in sync mode, if admission info is empty it should still match resources with specific clusterroles",
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterroles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: false,
},
{
Description: "Should fail since resource does not match policy",
AdmissionInfo: kyverno.RequestInfo{
ClusterRoles: []string{"admin"},
},
Resource: []byte(`{"apiVersion":"v1","kind":"Service","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterroles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: true,
},
{
Description: "Should not fail since resource does not match exclude block",
AdmissionInfo: kyverno.RequestInfo{
ClusterRoles: []string{"system:node"},
},
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world2","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterroles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: false,
},
}
for i, tc := range tcs {
var policy kyverno.Policy
err := json.Unmarshal(tc.Policy, &policy)
if err != nil {
t.Errorf("Testcase %d invalid policy raw", i+1)
}
resource, _ := utils.ConvertToUnstructured(tc.Resource)
for _, rule := range policy.Spec.Rules {
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo)
if err != nil {
if !tc.areErrorsExpected {
t.Errorf("Testcase %d Unexpected error: %v", i+1, err)
}
} else {
if tc.areErrorsExpected {
t.Errorf("Testcase %d Expected Error but recieved no error", i+1)
}
}
}
}
}
// Match multiple kinds
func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
rawResource := []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.7.9",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`)
resource, err := utils.ConvertToUnstructured(rawResource)
if err != nil {
t.Errorf("unable to convert raw resource to unstructured: %v", err)
}
resourceDescription := kyverno.ResourceDescription{
Kinds: []string{"Deployment", "Pods"},
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: nil,
},
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
// Match resource name
func TestResourceDescriptionMatch_Name(t *testing.T) {
rawResource := []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.7.9",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`)
resource, err := utils.ConvertToUnstructured(rawResource)
if err != nil {
t.Errorf("unable to convert raw resource to unstructured: %v", err)
}
resourceDescription := kyverno.ResourceDescription{
Kinds: []string{"Deployment"},
Name: "nginx-deployment",
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: nil,
},
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
// Match resource regex
func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
rawResource := []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.7.9",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`)
resource, err := utils.ConvertToUnstructured(rawResource)
if err != nil {
t.Errorf("unable to convert raw resource to unstructured: %v", err)
}
resourceDescription := kyverno.ResourceDescription{
Kinds: []string{"Deployment"},
Name: "nginx-*",
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: nil,
},
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
// Match expressions for labels to not match
func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
rawResource := []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.7.9",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`)
resource, err := utils.ConvertToUnstructured(rawResource)
if err != nil {
t.Errorf("unable to convert raw resource to unstructured: %v", err)
}
resourceDescription := kyverno.ResourceDescription{
Kinds: []string{"Deployment"},
Name: "nginx-*",
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "label2",
Operator: "NotIn",
Values: []string{
"sometest1",
},
},
},
},
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
// Match label expression in matching set
func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
rawResource := []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.7.9",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`)
resource, err := utils.ConvertToUnstructured(rawResource)
if err != nil {
t.Errorf("unable to convert raw resource to unstructured: %v", err)
}
resourceDescription := kyverno.ResourceDescription{
Kinds: []string{"Deployment"},
Name: "nginx-*",
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "app",
Operator: "NotIn",
Values: []string{
"nginx1",
"nginx2",
},
},
},
},
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
// check for exclude conditions
func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
rawResource := []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx",
"block": "true"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.7.9",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`)
resource, err := utils.ConvertToUnstructured(rawResource)
if err != nil {
t.Errorf("unable to convert raw resource to unstructured: %v", err)
}
resourceDescription := kyverno.ResourceDescription{
Kinds: []string{"Deployment"},
Name: "nginx-*",
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "app",
Operator: "NotIn",
Values: []string{
"nginx1",
"nginx2",
},
},
},
},
}
resourceDescriptionExclude := kyverno.ResourceDescription{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"block": "true",
},
},
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription},
ExcludeResources: kyverno.ExcludeResources{ResourceDescription: resourceDescriptionExclude}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err == nil {
t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was suposed to fail")
}
}
func Test_validateGeneralRuleInfoVariables(t *testing.T) {
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": {}
}
]
}
}
`)
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "test-validate-variables"
},
"spec": {
"rules": [
{
"name": "test-match",
"match": {
"Subjects": [
{
"kind": "User",
"name": "{{request.userInfo.username1}}}"
}
],
"resources": {
"kind": "{{request.object.kind}}"
}
}
},
{
"name": "test-exclude",
"match": {
"resources": {
"namespaces": [
"{{request.object.namespace}}"
]
}
}
},
{
"name": "test-condition",
"preconditions": [
{
"key": "{{serviceAccountName}}",
"operator": "NotEqual",
"value": "testuser"
}
]
}
]
}
}`)
userReqInfo := kyverno.RequestInfo{
AdmissionUserInfo: authenticationv1.UserInfo{
Username: "user1",
},
}
var policy kyverno.ClusterPolicy
assert.NilError(t, json.Unmarshal(policyRaw, &policy))
ctx := context.NewContext()
var err error
err = ctx.AddResource(rawResource)
if err != nil {
t.Error(err)
}
err = ctx.AddUserInfo(userReqInfo)
if err != nil {
t.Error(err)
}
err = ctx.AddSA("system:serviceaccount:test:testuser")
if err != nil {
t.Error(err)
}
expectPaths := []string{"request.userInfo.username1", "request.object.namespace", ""}
for i, rule := range policy.Spec.Rules {
invalidPaths := validateGeneralRuleInfoVariables(ctx, rule)
assert.Assert(t, invalidPaths == expectPaths[i], fmt.Sprintf("result not match, got invalidPaths %s", invalidPaths))
}
}