mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
2140a0239b
Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> Co-authored-by: Jim Bugwadia <jim@nirmata.com>
855 lines
17 KiB
Go
855 lines
17 KiB
Go
package validatingadmissionpolicy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/kyverno/kyverno/api/kyverno"
|
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
|
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
|
|
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
|
|
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
|
|
"gotest.tools/assert"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
func Test_Check_Resources(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
resource []byte
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "resource-with-namespaces",
|
|
resource: []byte(`
|
|
{
|
|
"kinds": [
|
|
"Service"
|
|
],
|
|
"namespaces": [
|
|
"prod"
|
|
],
|
|
"operations": [
|
|
"CREATE"
|
|
]
|
|
}
|
|
`),
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "namespaces-with-wildcards",
|
|
resource: []byte(`
|
|
{
|
|
"kinds": [
|
|
"Service"
|
|
],
|
|
"namespaces": [
|
|
"prod-*"
|
|
],
|
|
"operations": [
|
|
"CREATE"
|
|
]
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "resource-names-with-wildcards",
|
|
resource: []byte(`
|
|
{
|
|
"kinds": [
|
|
"Service"
|
|
],
|
|
"names": [
|
|
"svc-*"
|
|
],
|
|
"operations": [
|
|
"CREATE"
|
|
]
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "resource-with-annotations",
|
|
resource: []byte(`
|
|
{
|
|
"annotations": {
|
|
"imageregistry": "https://hub.docker.com/"
|
|
},
|
|
"kinds": [
|
|
"Pod"
|
|
],
|
|
"operations": [
|
|
"CREATE",
|
|
"UPDATE"
|
|
]
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "resource-with-object-selector",
|
|
resource: []byte(`
|
|
{
|
|
"kinds": [
|
|
"Pod"
|
|
],
|
|
"operations": [
|
|
"CREATE",
|
|
"UPDATE"
|
|
],
|
|
"selector": {
|
|
"matchLabels": {
|
|
"app": "critical"
|
|
}
|
|
}
|
|
}
|
|
`),
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "resource-with-namespace-selector",
|
|
resource: []byte(`
|
|
{
|
|
"kinds": [
|
|
"Pod"
|
|
],
|
|
"operations": [
|
|
"CREATE",
|
|
"UPDATE"
|
|
],
|
|
"namespaceSelector": {
|
|
"matchLabels": {
|
|
"app": "critical"
|
|
}
|
|
}
|
|
}
|
|
`),
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var res kyvernov1.ResourceDescription
|
|
err := json.Unmarshal(test.resource, &res)
|
|
assert.NilError(t, err)
|
|
out, _ := checkResources(res, true)
|
|
assert.Equal(t, out, test.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_Check_Exception(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
exceptions []kyvernov2.PolicyException
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "exception-with-multiple-policies",
|
|
exceptions: []kyvernov2.PolicyException{
|
|
{
|
|
Spec: kyvernov2.PolicyExceptionSpec{
|
|
Exceptions: []kyvernov2.Exception{
|
|
{
|
|
PolicyName: "test-1",
|
|
RuleNames: []string{"rule-1"},
|
|
},
|
|
{
|
|
PolicyName: "test-2",
|
|
RuleNames: []string{"rule-2"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "exception-with-multiple-rules",
|
|
exceptions: []kyvernov2.PolicyException{
|
|
{
|
|
Spec: kyvernov2.PolicyExceptionSpec{
|
|
Exceptions: []kyvernov2.Exception{
|
|
{
|
|
PolicyName: "test-1",
|
|
RuleNames: []string{"rule-1", "rule-2"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "exception-with-multiple-rules-in-different-exceptions",
|
|
exceptions: []kyvernov2.PolicyException{
|
|
{
|
|
Spec: kyvernov2.PolicyExceptionSpec{
|
|
Exceptions: []kyvernov2.Exception{
|
|
{
|
|
PolicyName: "test-1",
|
|
RuleNames: []string{"rule-1", "rule-2"},
|
|
},
|
|
{
|
|
PolicyName: "test-2",
|
|
RuleNames: []string{"rule-1"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "exception-with-conditions",
|
|
exceptions: []kyvernov2.PolicyException{
|
|
{
|
|
Spec: kyvernov2.PolicyExceptionSpec{
|
|
Exceptions: []kyvernov2.Exception{
|
|
{
|
|
PolicyName: "test-1",
|
|
RuleNames: []string{"rule-1"},
|
|
},
|
|
},
|
|
Conditions: &kyvernov2.AnyAllConditions{
|
|
AllConditions: []kyvernov2.Condition{
|
|
{
|
|
RawKey: &kyverno.Any{
|
|
Value: "{{ request.object.name }}",
|
|
},
|
|
Operator: kyvernov2.ConditionOperators["Equals"],
|
|
RawValue: &kyverno.Any{
|
|
Value: "dummy",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "exception-with-multiple-all",
|
|
exceptions: []kyvernov2.PolicyException{
|
|
{
|
|
Spec: kyvernov2.PolicyExceptionSpec{
|
|
Exceptions: []kyvernov2.Exception{
|
|
{
|
|
PolicyName: "test-1",
|
|
RuleNames: []string{"rule-1"},
|
|
},
|
|
},
|
|
Match: kyvernov2beta1.MatchResources{
|
|
All: kyvernov1.ResourceFilters{
|
|
kyvernov1.ResourceFilter{
|
|
ResourceDescription: kyvernov1.ResourceDescription{
|
|
Kinds: []string{"Pod"},
|
|
Operations: []kyvernov1.AdmissionOperation{"CREATE"},
|
|
},
|
|
},
|
|
kyvernov1.ResourceFilter{
|
|
ResourceDescription: kyvernov1.ResourceDescription{
|
|
Kinds: []string{"Pod"},
|
|
Operations: []kyvernov1.AdmissionOperation{"CREATE"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "exception-with-namespace-selector",
|
|
exceptions: []kyvernov2.PolicyException{
|
|
{
|
|
Spec: kyvernov2.PolicyExceptionSpec{
|
|
Exceptions: []kyvernov2.Exception{
|
|
{
|
|
PolicyName: "test-1",
|
|
RuleNames: []string{"rule-1"},
|
|
},
|
|
},
|
|
Match: kyvernov2beta1.MatchResources{
|
|
Any: kyvernov1.ResourceFilters{
|
|
kyvernov1.ResourceFilter{
|
|
ResourceDescription: kyvernov1.ResourceDescription{
|
|
Kinds: []string{"Pod"},
|
|
Operations: []kyvernov1.AdmissionOperation{"CREATE"},
|
|
NamespaceSelector: &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{
|
|
"app": "critical",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "exception-with-object-selector",
|
|
exceptions: []kyvernov2.PolicyException{
|
|
{
|
|
Spec: kyvernov2.PolicyExceptionSpec{
|
|
Exceptions: []kyvernov2.Exception{
|
|
{
|
|
PolicyName: "test-1",
|
|
RuleNames: []string{"rule-1"},
|
|
},
|
|
},
|
|
Match: kyvernov2beta1.MatchResources{
|
|
Any: kyvernov1.ResourceFilters{
|
|
kyvernov1.ResourceFilter{
|
|
ResourceDescription: kyvernov1.ResourceDescription{
|
|
Kinds: []string{"Pod"},
|
|
Operations: []kyvernov1.AdmissionOperation{"CREATE"},
|
|
Selector: &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{
|
|
"app": "critical",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "exception-with-multiple-any",
|
|
exceptions: []kyvernov2.PolicyException{
|
|
{
|
|
Spec: kyvernov2.PolicyExceptionSpec{
|
|
Exceptions: []kyvernov2.Exception{
|
|
{
|
|
PolicyName: "test-1",
|
|
RuleNames: []string{"rule-1"},
|
|
},
|
|
},
|
|
Match: kyvernov2beta1.MatchResources{
|
|
Any: kyvernov1.ResourceFilters{
|
|
kyvernov1.ResourceFilter{
|
|
ResourceDescription: kyvernov1.ResourceDescription{
|
|
Kinds: []string{"Pod"},
|
|
Operations: []kyvernov1.AdmissionOperation{"CREATE"},
|
|
},
|
|
},
|
|
kyvernov1.ResourceFilter{
|
|
ResourceDescription: kyvernov1.ResourceDescription{
|
|
Kinds: []string{"Pod"},
|
|
Operations: []kyvernov1.AdmissionOperation{"CREATE"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
}
|
|
for _, test := range testCases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
out, _ := checkExceptions(test.exceptions)
|
|
assert.Equal(t, out, test.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_Can_Generate_ValidatingAdmissionPolicy(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
policy []byte
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "policy-with-two-rules",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "disallow-latest-tag"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "require-image-tag",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"cel": {
|
|
"expressions": [
|
|
{
|
|
"expression": "object.spec.containers.all(container, !container.image.matches('^[a-zA-Z]+:[0-9]*$'))"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "validate-image-tag",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"cel": {
|
|
"expressions": [
|
|
{
|
|
"expression": "object.spec.containers.all(container, !container.image.contains('latest'))"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "policy-with-mutate-rule",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "set-image-pull-policy"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "set-image-pull-policy",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"mutate": {
|
|
"patchStrategicMerge": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"(image)": "*:latest",
|
|
"imagePullPolicy": "IfNotPresent"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "policy-with-non-CEL-validate-rule",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "require-ns-purpose-label"
|
|
},
|
|
"spec": {
|
|
"validationFailureAction": "Enforce",
|
|
"rules": [
|
|
{
|
|
"name": "require-ns-purpose-label",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Namespace"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"pattern": {
|
|
"metadata": {
|
|
"labels": {
|
|
"purpose": "production"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "policy-with-multiple-validationFailureActionOverrides",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "disallow-host-path"
|
|
},
|
|
"spec": {
|
|
"validationFailureAction": "Enforce",
|
|
"validationFailureActionOverrides": [
|
|
{
|
|
"action": "Enforce",
|
|
"namespaces": [
|
|
"default"
|
|
]
|
|
},
|
|
{
|
|
"action": "Audit",
|
|
"namespaces": [
|
|
"test"
|
|
]
|
|
}
|
|
],
|
|
"rules": [
|
|
{
|
|
"name": "host-path",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"cel": {
|
|
"expressions": [
|
|
{
|
|
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "policy-with-namespace-in-validationFailureActionOverrides",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "disallow-host-path"
|
|
},
|
|
"spec": {
|
|
"validationFailureAction": "Enforce",
|
|
"validationFailureActionOverrides": [
|
|
{
|
|
"action": "Enforce",
|
|
"namespaces": [
|
|
"test-ns"
|
|
]
|
|
}
|
|
],
|
|
"rules": [
|
|
{
|
|
"name": "host-path",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"cel": {
|
|
"expressions": [
|
|
{
|
|
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "policy-with-multiple-validationFailureActionOverrides-in-validate-rule",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "disallow-host-path"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "host-path",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"failureAction": "Enforce",
|
|
"failureActionOverrides": [
|
|
{
|
|
"action": "Enforce",
|
|
"namespaces": [
|
|
"default"
|
|
]
|
|
},
|
|
{
|
|
"action": "Audit",
|
|
"namespaces": [
|
|
"test"
|
|
]
|
|
}
|
|
],
|
|
"cel": {
|
|
"expressions": [
|
|
{
|
|
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "policy-with-namespace-in-validationFailureActionOverrides-in-validate-rule",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "disallow-host-path"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "host-path",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"failureAction": "Enforce",
|
|
"failureActionOverrides": [
|
|
{
|
|
"action": "Enforce",
|
|
"namespaces": [
|
|
"test-ns"
|
|
]
|
|
}
|
|
],
|
|
"cel": {
|
|
"expressions": [
|
|
{
|
|
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "policy-with-subjects-and-clusterroles",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "disallow-host-path"
|
|
},
|
|
"spec": {
|
|
"validationFailureAction": "Enforce",
|
|
"rules": [
|
|
{
|
|
"name": "host-path",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Deployment"
|
|
],
|
|
"operations": [
|
|
"CREATE",
|
|
"UPDATE"
|
|
]
|
|
},
|
|
"subjects": [
|
|
{
|
|
"kind": "User",
|
|
"name": "mary@somecorp.com"
|
|
}
|
|
],
|
|
"clusterRoles": [
|
|
"cluster-admin"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"cel": {
|
|
"expressions": [
|
|
{
|
|
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "policy-with-object-selector",
|
|
policy: []byte(`
|
|
{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "disallow-host-path"
|
|
},
|
|
"spec": {
|
|
"validationFailureAction": "Enforce",
|
|
"rules": [
|
|
{
|
|
"name": "host-path",
|
|
"match": {
|
|
"any": [
|
|
{
|
|
"resources": {
|
|
"kinds": [
|
|
"Deployment"
|
|
],
|
|
"operations": [
|
|
"CREATE",
|
|
"UPDATE"
|
|
],
|
|
"selector": {
|
|
"matchLabels": {
|
|
"app": "mongodb"
|
|
},
|
|
"matchExpressions": [
|
|
{
|
|
"key": "tier",
|
|
"operator": "In",
|
|
"values": [
|
|
"database"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"validate": {
|
|
"cel": {
|
|
"expressions": [
|
|
{
|
|
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`),
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
policies, _, _, err := yamlutils.GetPolicy([]byte(test.policy))
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, 1, len(policies))
|
|
out, _ := CanGenerateVAP(policies[0].GetSpec(), nil)
|
|
assert.Equal(t, out, test.expected)
|
|
})
|
|
}
|
|
}
|