mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
d47684c0d9
* feat: support validatingadmissionpolicybindings in CLI apply command Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> * fix linter issue Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> --------- Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
489 lines
12 KiB
Go
489 lines
12 KiB
Go
package policy
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
|
|
"gotest.tools/assert"
|
|
"k8s.io/apimachinery/pkg/util/yaml"
|
|
)
|
|
|
|
func TestNotAllowedVars_MatchSection(t *testing.T) {
|
|
var policyWithVarInMatch = []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "enforce-pod-name"
|
|
},
|
|
"spec": {
|
|
"validationFailureAction": "audit",
|
|
"background": true,
|
|
"rules": [
|
|
{
|
|
"name": "validate-name",
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
],
|
|
"namespaceSelector": {
|
|
"matchExpressions": [
|
|
{
|
|
"key": "{{very.unusual.variable.here}}",
|
|
"operator": "In",
|
|
"values": [
|
|
"managed"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "The Pod must end with -nginx",
|
|
"pattern": {
|
|
"metadata": {
|
|
"name": "*-nginx"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInMatch)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.Error(t, err, "rule \"validate-name\" should not have variables in match section")
|
|
}
|
|
|
|
func TestNotAllowedVars_ExcludeSection(t *testing.T) {
|
|
var policyWithVarInExclude = []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "enforce-pod-name"
|
|
},
|
|
"spec": {
|
|
"validationFailureAction": "audit",
|
|
"background": true,
|
|
"rules": [
|
|
{
|
|
"name": "validate-name",
|
|
"exclude": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
],
|
|
"namespaceSelector": {
|
|
"matchExpressions": [
|
|
{
|
|
"key": "value",
|
|
"operator": "In",
|
|
"values": [
|
|
"{{very.unusual.variable.here}}"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "The Pod must end with -nginx",
|
|
"pattern": {
|
|
"metadata": {
|
|
"name": "*-nginx"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.Error(t, err, "rule \"validate-name\" should not have variables in exclude section")
|
|
}
|
|
|
|
func TestNotAllowedVars_ExcludeSection_PositiveCase(t *testing.T) {
|
|
var policyWithVarInExclude = []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "enforce-pod-name"
|
|
},
|
|
"spec": {
|
|
"validationFailureAction": "audit",
|
|
"background": true,
|
|
"rules": [
|
|
{
|
|
"name": "validate-name",
|
|
"exclude": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Pod"
|
|
],
|
|
"namespaceSelector": {
|
|
"matchExpressions": [
|
|
{
|
|
"key": "value",
|
|
"operator": "In",
|
|
"values": [
|
|
"value1",
|
|
"value2"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "The Pod must end with -nginx",
|
|
"pattern": {
|
|
"metadata": {
|
|
"name": "*-nginx"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestNotAllowedVars_JSONPatchPath(t *testing.T) {
|
|
var policyWithVarInExclude = []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-patch-cm"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pCM1",
|
|
"match": {
|
|
"resources": {
|
|
"name": "config-game",
|
|
"kinds": [
|
|
"ConfigMap"
|
|
]
|
|
}
|
|
},
|
|
"mutate": {
|
|
"patchesJson6902": "- path: \"{{request.object.path.root}}/data/ship.properties\"\n op: add\n value: |\n type=starship\n owner=utany.corp\n- path: \"/data/newKey1\"\n op: add\n value: newValue1"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.Error(t, err, "rule \"pCM1\" should not have variables in patchesJSON6902 path section")
|
|
}
|
|
|
|
func TestNotAllowedVars_JSONPatchPath_ContextRootPositive(t *testing.T) {
|
|
var policyManifest = []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-patch-cm"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pCM1",
|
|
"context": [
|
|
{
|
|
"name": "source",
|
|
"configMap":{
|
|
"name":"global-config",
|
|
"namespace":"default"
|
|
}
|
|
}
|
|
],
|
|
"match": {
|
|
"resources": {
|
|
"name": "config-game",
|
|
"kinds": [
|
|
"ConfigMap"
|
|
]
|
|
}
|
|
},
|
|
"mutate": {
|
|
"patchStrategicMerge": {
|
|
"data": "{{ source.data }}"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyManifest)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestNotAllowedVars_JSONPatchPath_ContextSubPositive(t *testing.T) {
|
|
var policyManifest = []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-patch-cm"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pCM2",
|
|
"context": [
|
|
{
|
|
"name": "source",
|
|
"configMap":{
|
|
"name":"source-yaml-to-json",
|
|
"namespace":"default"
|
|
}
|
|
}
|
|
],
|
|
"match": {
|
|
"resources": {
|
|
"name": "config-game",
|
|
"kinds": [
|
|
"ConfigMap"
|
|
]
|
|
}
|
|
},
|
|
"mutate": {
|
|
"patchesJson6902": "- op: replace\n path: /spec/strategy\n value: {{ source.data.strategy }}"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyManifest)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestNotAllowedVars_JSONPatchPath_PositiveCase(t *testing.T) {
|
|
var policyWithVarInExclude = []byte(`{
|
|
"apiVersion": "kyverno.io/v1",
|
|
"kind": "ClusterPolicy",
|
|
"metadata": {
|
|
"name": "policy-patch-cm"
|
|
},
|
|
"spec": {
|
|
"rules": [
|
|
{
|
|
"name": "pCM1",
|
|
"match": {
|
|
"resources": {
|
|
"name": "config-game",
|
|
"kinds": [
|
|
"ConfigMap"
|
|
]
|
|
}
|
|
},
|
|
"mutate": {
|
|
"patchesJson6902": "- path: \"/data/ship.properties\"\n op: add\n value: |\n type={{request.object.starship}}\n owner=utany.corp\n- path: \"/data/newKey1\"\n op: add\n value: newValue1"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyWithVarInExclude)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestNotAllowedVars_JSONPatchPath_PositiveCaseWithValue(t *testing.T) {
|
|
var policyYAML = []byte(`
|
|
apiVersion: kyverno.io/v1
|
|
kind: ClusterPolicy
|
|
metadata:
|
|
name: mutate-ingress-host
|
|
spec:
|
|
rules:
|
|
- name: mutate-rules-host
|
|
match:
|
|
resources:
|
|
kinds:
|
|
- Ingress
|
|
namespaces:
|
|
- test-ingress
|
|
mutate:
|
|
patchesJson6902: |-
|
|
- op: replace
|
|
path: /spec/rules/0/host
|
|
value: "{{request.object.spec.rules[0].host}}.mycompany.com"
|
|
`)
|
|
|
|
policyJSON, err := yaml.ToJSON(policyYAML)
|
|
assert.NilError(t, err)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyJSON)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestNotAllowedVars_VariableFormats(t *testing.T) {
|
|
tcs := []struct {
|
|
name string
|
|
input string
|
|
pass bool
|
|
}{
|
|
{"invalid_var", "not a valid variable", false},
|
|
{"request_object", "request.object.meta", true},
|
|
{"service_account_name", "serviceAccountName", true},
|
|
{"service_account_namespace", "serviceAccountNamespace", true},
|
|
{"self", "@", true},
|
|
{"custom_func_compare", "compare(string, string)", true},
|
|
{"custom_func_contains", "contains(string, string)", true},
|
|
{"custom_func_equal_fold", "equal_fold(string, string)", true},
|
|
{"custom_func_replace", "replace(str string, old string, new string, n float64)", true},
|
|
{"custom_func_replace_all", "replace_all(str string, old string, new string)", true},
|
|
{"custom_func_to_upper", "to_upper(string)", true},
|
|
{"custom_func_to_lower", "to_lower(string)", true},
|
|
{"custom_func_trim", "trim(str string, cutset string)", true},
|
|
{"custom_func_trim_prefix", "trim_prefix(str string, prefix string)", true},
|
|
{"custom_func_split", "split(str string, sep string)", true},
|
|
{"custom_func_regex_replace_all", "regex_replace_all(regex string, src string|number, replace string|number)", true},
|
|
{"custom_func_regex_replace_all_literal", "regex_replace_all_literal(regex string, src string|number, replace string|number)", true},
|
|
{"custom_func_regex_match", "regex_match(string, string|number)", true},
|
|
{"custom_func_label_match", "label_match(object, object)", true},
|
|
{"abs", "abs(foo, bar)", true},
|
|
{"avg", "avg(foo, bar)", true},
|
|
{"contains", "contains(foo, bar)", true},
|
|
{"ceil", "ceil(foo, bar)", true},
|
|
{"ends_with", "ends_with(foo, bar)", true},
|
|
{"floor", "floor(foo, bar)", true},
|
|
{"join", "join(foo, bar)", true},
|
|
{"keys", "keys(foo, bar)", true},
|
|
{"length", "length(foo, bar)", true},
|
|
{"map", "map(foo, bar)", true},
|
|
{"max", "max(foo, bar)", true},
|
|
{"max_by", "max_by(foo, bar)", true},
|
|
{"merge", "merge(foo, bar)", true},
|
|
{"min", "min(foo, bar)", true},
|
|
{"min_by", "min_by(foo, bar)", true},
|
|
{"not_null", "not_null(foo, bar)", true},
|
|
{"reverse", "reverse(foo, bar)", true},
|
|
{"sort", "sort(foo, bar)", true},
|
|
{"sort_by", "sort_by(foo, bar)", true},
|
|
{"starts_with", "starts_with(foo, bar)", true},
|
|
{"sum", "sum(foo, bar)", true},
|
|
{"to_array", "to_array(foo, bar)", true},
|
|
{"to_string", "to_string(foo, bar)", true},
|
|
{"to_number", "to_number(foo, bar)", true},
|
|
{"type", "type(foo, bar)", true},
|
|
{"values", "values(foo, bar)", true},
|
|
{"self_path_test", "@", true},
|
|
}
|
|
|
|
for _, tc := range tcs {
|
|
var policyYAML = []byte(fmt.Sprintf(`
|
|
apiVersion: kyverno.io/v1
|
|
kind: ClusterPolicy
|
|
metadata:
|
|
name: mutate-ingress-host
|
|
spec:
|
|
rules:
|
|
- name: mutate-rules-host
|
|
match:
|
|
resources:
|
|
kinds:
|
|
- Ingress
|
|
namespaces:
|
|
- test-ingress
|
|
preconditions:
|
|
any:
|
|
- key: "{{ %s }}"
|
|
operator: NotEquals
|
|
value: ""
|
|
mutate:
|
|
patchesJson6902: |-
|
|
- op: replace
|
|
path: /spec/rules/0/host
|
|
value: "foo.com"
|
|
`, tc.input))
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyYAML)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
if tc.pass {
|
|
assert.NilError(t, err, "%s: not expecting an error", tc.name)
|
|
} else {
|
|
assert.Assert(t, err != nil, "%s: was expecting an error", tc.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNotAllowedVars_Attestations(t *testing.T) {
|
|
var policyYAML = []byte(`
|
|
---
|
|
apiVersion: kyverno.io/v1
|
|
kind: ClusterPolicy
|
|
metadata:
|
|
name: attest-bom
|
|
spec:
|
|
validationFailureAction: enforce
|
|
webhookTimeoutSeconds: 30
|
|
failurePolicy: Fail
|
|
rules:
|
|
- name: check-attestations
|
|
match:
|
|
resources:
|
|
kinds:
|
|
- Pod
|
|
verifyImages:
|
|
- imageReferences:
|
|
- "ghcr.io/test/demo-java-tomcat:*"
|
|
attestations:
|
|
- predicateType: "https://cyclonedx.org/BOM/v1"
|
|
- predicateType: "https://trivy.aquasec.com/scan/v2"
|
|
conditions:
|
|
- all:
|
|
- key: "{{ scanner }}"
|
|
operator: Equals
|
|
value: trivy
|
|
- predicateType: https://example.com/provenance/v1
|
|
`)
|
|
|
|
policyJSON, err := yaml.ToJSON(policyYAML)
|
|
assert.NilError(t, err)
|
|
|
|
policy, _, _, err := yamlutils.GetPolicy(policyJSON)
|
|
assert.NilError(t, err)
|
|
|
|
err = hasInvalidVariables(policy[0], false)
|
|
assert.NilError(t, err)
|
|
}
|