mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 16:06:56 +00:00
Merge branch 'main' of https://github.com/kyverno/kyverno into main
This commit is contained in:
commit
445394a442
54 changed files with 1327 additions and 452 deletions
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -65,7 +65,7 @@ them, don't hesitate to ask. We're here to help! This is simply a reminder of wh
|
|||
- [] I have raised an issue in [kyverno/website](https://github.com/kyverno/website) to track the doc update and the link is:
|
||||
<!-- Uncomment to link to the issue -->
|
||||
<!-- https://github.com/kyverno/website/issues/1 -->
|
||||
- [] I have read the [PR documentation guide](pr_documentation.md) and followed the process including adding proof manifests to this PR.
|
||||
- [] I have read the [PR documentation guide](https://github.com/kyverno/kyverno/blob/main/.github/pr_documentation.md) and followed the process including adding proof manifests to this PR.
|
||||
|
||||
## Further Comments
|
||||
|
||||
|
|
9
Makefile
9
Makefile
|
@ -176,9 +176,12 @@ test-e2e:
|
|||
$(eval export E2E="")
|
||||
|
||||
#Test TestCmd Policy
|
||||
run_testcmd_policy:
|
||||
go build -o kyvernoctl cmd/cli/kubectl-kyverno/main.go
|
||||
./kyvernoctl test https://github.com/kyverno/policies/main
|
||||
run_testcmd_policy: cli
|
||||
$(PWD)/$(CLI_PATH)/kyverno test https://github.com/kyverno/policies/main
|
||||
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test
|
||||
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-policy && exit 1 || exit 0
|
||||
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-rule && exit 1 || exit 0
|
||||
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-resource && exit 1 || exit 0
|
||||
|
||||
# godownloader create downloading script for kyverno-cli
|
||||
godownloader:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
apiVersion: v1
|
||||
name: kyverno
|
||||
version: v1.3.6-rc1
|
||||
appVersion: v1.3.6-rc1
|
||||
version: v1.3.6-rc3
|
||||
appVersion: v1.3.6-rc3
|
||||
icon: https://github.com/kyverno/kyverno/raw/main/img/logo.png
|
||||
description: Kubernetes Native Policy Management
|
||||
keywords:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Thank you for installing {{ .Chart.Name }} 😀
|
||||
Thank you for installing {{ .Chart.Name }} {{ .Chart.Version }} 😀
|
||||
|
||||
Your release is named {{ .Release.Name }}.
|
||||
|
||||
|
|
|
@ -47,6 +47,9 @@ spec:
|
|||
- name: kyverno-pre
|
||||
image: {{ .Values.initImage.repository }}:{{ default .Chart.AppVersion (default .Values.image.tag .Values.initImage.tag) }}
|
||||
imagePullPolicy: {{ default .Values.image.pullPolicy .Values.initImage.pullPolicy }}
|
||||
{{- with .Values.initResources }}
|
||||
resources: {{ tpl (toYaml .) $ | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
runAsNonRoot: true
|
||||
|
|
|
@ -71,6 +71,14 @@ resources:
|
|||
cpu: 100m
|
||||
memory: 50Mi
|
||||
|
||||
initResources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
|
||||
## Liveness Probe. The block is directly forwarded into the deployment, so you can use whatever livenessProbe configuration you want.
|
||||
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
|
||||
##
|
||||
|
|
|
@ -148,8 +148,7 @@ func main() {
|
|||
debug,
|
||||
log.Log)
|
||||
|
||||
// Resource Mutating Webhook Watcher
|
||||
webhookMonitor := webhookconfig.NewMonitor(rCache, log.Log.WithName("WebhookMonitor"))
|
||||
webhookMonitor := webhookconfig.NewMonitor(kubeInformer.Core().V1().Secrets(), log.Log.WithName("WebhookMonitor"))
|
||||
|
||||
// KYVERNO CRD INFORMER
|
||||
// watches CRD resources:
|
||||
|
|
|
@ -2441,7 +2441,7 @@ spec:
|
|||
fieldPath: metadata.namespace
|
||||
- name: KYVERNO_SVC
|
||||
value: kyverno-svc
|
||||
image: ghcr.io/kyverno/kyverno:v1.3.6-rc1
|
||||
image: ghcr.io/kyverno/kyverno:v1.3.6-rc3
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
failureThreshold: 2
|
||||
|
@ -2483,9 +2483,16 @@ spec:
|
|||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
initContainers:
|
||||
- image: ghcr.io/kyverno/kyvernopre:v1.3.6-rc1
|
||||
- image: ghcr.io/kyverno/kyvernopre:v1.3.6-rc3
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: kyverno-pre
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
|
|
|
@ -8,7 +8,7 @@ resources:
|
|||
images:
|
||||
- name: ghcr.io/kyverno/kyverno
|
||||
newName: ghcr.io/kyverno/kyverno
|
||||
newTag: v1.3.6-rc1
|
||||
newTag: v1.3.6-rc3
|
||||
- name: ghcr.io/kyverno/kyvernopre
|
||||
newName: ghcr.io/kyverno/kyvernopre
|
||||
newTag: v1.3.6-rc1
|
||||
newTag: v1.3.6-rc3
|
||||
|
|
|
@ -26,6 +26,13 @@ spec:
|
|||
- name: kyverno-pre
|
||||
image: ghcr.io/kyverno/kyvernopre:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
|
|
|
@ -2441,7 +2441,7 @@ spec:
|
|||
fieldPath: metadata.namespace
|
||||
- name: KYVERNO_SVC
|
||||
value: kyverno-svc
|
||||
image: ghcr.io/kyverno/kyverno:v1.3.6-rc1
|
||||
image: ghcr.io/kyverno/kyverno:v1.3.6-rc3
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
failureThreshold: 2
|
||||
|
@ -2483,9 +2483,16 @@ spec:
|
|||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
initContainers:
|
||||
- image: ghcr.io/kyverno/kyvernopre:v1.3.6-rc1
|
||||
- image: ghcr.io/kyverno/kyvernopre:v1.3.6-rc3
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: kyverno-pre
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
|
|
1
go.sum
1
go.sum
|
@ -744,6 +744,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
|||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e/go.mod h1:d8hQseuYt4rJoOo21lFzYJdhMjmDqLY++ayArbgYjWI=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
|
|
|
@ -103,7 +103,7 @@ func VariableToJSON(key, value string) []byte {
|
|||
}
|
||||
}
|
||||
|
||||
midString := fmt.Sprintf(`"%s"`, value)
|
||||
midString := fmt.Sprintf(`"%s"`, strings.Replace(value, `"`, `\"`, -1))
|
||||
finalString := startString + midString + endString
|
||||
var jsonData = []byte(finalString)
|
||||
return jsonData
|
||||
|
|
|
@ -88,14 +88,6 @@ func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resour
|
|||
}
|
||||
}
|
||||
|
||||
if rule.Mutation.PatchStrategicMerge != nil {
|
||||
var resp response.RuleResponse
|
||||
resp, resource = mutate.ProcessStrategicMergePatch(rule.Name, rule.Mutation.PatchStrategicMerge, resource, logger.WithValues("rule", rule.Name))
|
||||
if !resp.Success {
|
||||
return unstructured.Unstructured{}, fmt.Errorf(resp.Message)
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Mutation.PatchesJSON6902 != "" {
|
||||
var resp response.RuleResponse
|
||||
jsonPatches, err := yaml.YAMLToJSON([]byte(rule.Mutation.PatchesJSON6902))
|
||||
|
|
|
@ -150,92 +150,6 @@ func Test_ForceMutateSubstituteVarsWithNilContext(t *testing.T) {
|
|||
assert.DeepEqual(t, expectedResource, mutatedResource.UnstructuredContent())
|
||||
}
|
||||
|
||||
func Test_ForceMutateSubstituteVarsWithPatchStrategicMerge(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "strategic-merge-patch"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "set-image-pull-policy-add-command",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mutate": {
|
||||
"patchStrategicMerge": {
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"emptyDir": {
|
||||
"medium": "Memory"
|
||||
},
|
||||
"name": "cache-volume"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
rawResource := []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "check-root-user"
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"name": "cache-volume",
|
||||
"emptyDir": { }
|
||||
},
|
||||
{
|
||||
"name": "cache-volume2",
|
||||
"emptyDir": {
|
||||
"medium": "Memory"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
expectedRawResource := []byte(`
|
||||
{"apiVersion":"v1","kind":"Pod","metadata":{"name":"check-root-user"},"spec":{"volumes":[{"emptyDir":{"medium":"Memory"},"name":"cache-volume"},{"emptyDir":{"medium":"Memory"},"name":"cache-volume2"}]}}
|
||||
`)
|
||||
|
||||
var expectedResource interface{}
|
||||
assert.NilError(t, json.Unmarshal(expectedRawResource, &expectedResource))
|
||||
|
||||
var policy kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
ctx := context.NewContext()
|
||||
err = ctx.AddResource(rawResource)
|
||||
assert.NilError(t, err)
|
||||
|
||||
mutatedResource, err := ForceMutate(ctx, policy, *resourceUnstructured)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.DeepEqual(t, expectedResource, mutatedResource.UnstructuredContent())
|
||||
}
|
||||
|
||||
func Test_ForceMutateSubstituteVarsWithPatchesJson6902(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
|
|
|
@ -39,6 +39,7 @@ var (
|
|||
regexReplaceAll = "regex_replace_all"
|
||||
regexReplaceAllLiteral = "regex_replace_all_literal"
|
||||
regexMatch = "regex_match"
|
||||
labelMatch = "label_match"
|
||||
)
|
||||
|
||||
const errorPrefix = "JMESPath function '%s': "
|
||||
|
@ -146,6 +147,15 @@ func getFunctions() []*gojmespath.FunctionEntry {
|
|||
},
|
||||
Handler: jpRegexMatch,
|
||||
},
|
||||
{
|
||||
// Validates if label (param1) would match pod/host/etc labels (param2)
|
||||
Name: labelMatch,
|
||||
Arguments: []ArgSpec{
|
||||
{Types: []JpType{JpObject}},
|
||||
{Types: []JpType{JpObject}},
|
||||
},
|
||||
Handler: jpLabelMatch,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -353,6 +363,28 @@ func jpRegexMatch(arguments []interface{}) (interface{}, error) {
|
|||
return regexp.Match(regex.String(), []byte(src))
|
||||
}
|
||||
|
||||
func jpLabelMatch(arguments []interface{}) (interface{}, error) {
|
||||
labelMap, ok := arguments[0].(map[string]interface{})
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(invalidArgumentTypeError, labelMatch, 0, "Object")
|
||||
}
|
||||
|
||||
matchMap, ok := arguments[1].(map[string]interface{})
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(invalidArgumentTypeError, labelMatch, 1, "Object")
|
||||
}
|
||||
|
||||
for key, value := range labelMap {
|
||||
if val, ok := matchMap[key]; !ok || val != value {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InterfaceToString casts an interface to a string type
|
||||
func ifaceToString(iface interface{}) (string, error) {
|
||||
switch iface.(type) {
|
||||
|
|
|
@ -243,3 +243,61 @@ func Test_regexReplaceAllLiteral(t *testing.T) {
|
|||
|
||||
assert.Equal(t, string(result), expected)
|
||||
}
|
||||
|
||||
func Test_labelMatch(t *testing.T) {
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "test-app",
|
||||
"controller-name": "test-controller"
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
testCases := []struct {
|
||||
resource []byte
|
||||
test string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
resource: resourceRaw,
|
||||
test: `{ "app": "test-app" }`,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
resource: resourceRaw,
|
||||
test: `{ "app": "test-app", "controller-name": "test-controller" }`,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
resource: resourceRaw,
|
||||
test: `{ "app": "test-app2" }`,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
resource: resourceRaw,
|
||||
test: `{ "app.kubernetes.io/name": "test-app" }`,
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
var resource interface{}
|
||||
err := json.Unmarshal(testCase.resource, &resource)
|
||||
assert.NilError(t, err)
|
||||
|
||||
query, err := New("label_match(`" + testCase.test + "`, metadata.labels)")
|
||||
assert.NilError(t, err)
|
||||
|
||||
res, err := query.Search(resource)
|
||||
assert.NilError(t, err)
|
||||
|
||||
result, ok := res.(bool)
|
||||
assert.Assert(t, ok)
|
||||
|
||||
assert.Equal(t, result, testCase.expectedResult)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ func LoadContext(logger logr.Logger, contextEntries []kyverno.ContextEntry, resC
|
|||
policyName := ctx.Policy.Name
|
||||
if store.GetMock() {
|
||||
rule := store.GetPolicyRuleFromContext(policyName, ruleName)
|
||||
if len(rule.Values) == 0 {
|
||||
return fmt.Errorf("No values found for policy %s rule %s", policyName, ruleName)
|
||||
}
|
||||
variables := rule.Values
|
||||
|
||||
for key, value := range variables {
|
||||
|
|
|
@ -411,16 +411,14 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
|
|||
label["policy.kyverno.io/synchronize"] = "disable"
|
||||
}
|
||||
|
||||
if rule.Generation.Synchronize {
|
||||
logger.V(4).Info("updating existing resource")
|
||||
newResource.SetLabels(label)
|
||||
_, err := client.UpdateResource(genAPIVersion, genKind, genNamespace, newResource, false)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to update resource")
|
||||
return noGenResource, err
|
||||
}
|
||||
logger.V(2).Info("updated target resource")
|
||||
logger.V(4).Info("updating label in existing resource")
|
||||
newResource.SetLabels(label)
|
||||
_, err := client.UpdateResource(genAPIVersion, genKind, genNamespace, newResource, false)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to update resource")
|
||||
return noGenResource, err
|
||||
}
|
||||
logger.V(2).Info("updated target resource")
|
||||
}
|
||||
|
||||
return newGenResource, nil
|
||||
|
|
|
@ -334,7 +334,7 @@ func GetVariable(variablesString, valuesFile string, fs billy.Filesystem, isGit
|
|||
}
|
||||
yamlFile, err = ioutil.ReadAll(filep)
|
||||
} else {
|
||||
yamlFile, err = ioutil.ReadFile(valuesFile)
|
||||
yamlFile, err = ioutil.ReadFile(filepath.Join(policyresoucePath, valuesFile))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -122,9 +122,6 @@ const PolicyCRD = `
|
|||
"description": "ResourceDescription contains information about the resource being created or modified.",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters \"*\" (matches zero or many characters) and \"?\" (matches at least one character).",
|
||||
"type": "object"
|
||||
},
|
||||
|
@ -178,9 +175,6 @@ const PolicyCRD = `
|
|||
"type": "array"
|
||||
},
|
||||
"matchLabels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.",
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -233,9 +227,6 @@ const PolicyCRD = `
|
|||
"type": "array"
|
||||
},
|
||||
"matchLabels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.",
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -349,9 +340,6 @@ const PolicyCRD = `
|
|||
"description": "ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources.",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters \"*\" (matches zero or many characters) and \"?\" (matches at least one character).",
|
||||
"type": "object"
|
||||
},
|
||||
|
@ -405,9 +393,6 @@ const PolicyCRD = `
|
|||
"type": "array"
|
||||
},
|
||||
"matchLabels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.",
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -460,9 +445,6 @@ const PolicyCRD = `
|
|||
"type": "array"
|
||||
},
|
||||
"matchLabels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.",
|
||||
"type": "object"
|
||||
}
|
||||
|
|
|
@ -22,9 +22,11 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
|
||||
"github.com/kyverno/kyverno/pkg/kyverno/store"
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
||||
"github.com/kyverno/kyverno/pkg/policyreport"
|
||||
util "github.com/kyverno/kyverno/pkg/utils"
|
||||
"github.com/lensesio/tableprinter"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -75,10 +77,10 @@ type SkippedPolicy struct {
|
|||
}
|
||||
|
||||
type TestResults struct {
|
||||
Policy string `json:"policy"`
|
||||
Rule string `json:"rule"`
|
||||
Status string `json:"status"`
|
||||
Resource string `json:"resource"`
|
||||
Policy string `json:"policy"`
|
||||
Rule string `json:"rule"`
|
||||
Status report.PolicyStatus `json:"status"`
|
||||
Resource string `json:"resource"`
|
||||
}
|
||||
|
||||
type ReportResult struct {
|
||||
|
@ -106,6 +108,7 @@ type Values struct {
|
|||
}
|
||||
|
||||
type resultCounts struct {
|
||||
skip int
|
||||
pass int
|
||||
fail int
|
||||
}
|
||||
|
@ -145,105 +148,125 @@ func testCommandExecute(dirPath []string, valuesFile string, fileName string) (r
|
|||
sort.Strings(policyYamls)
|
||||
for _, yamlFilePath := range policyYamls {
|
||||
file, err := fs.Open(yamlFilePath)
|
||||
if err != nil {
|
||||
errors = append(errors, sanitizederror.NewWithError("Error: failed to open file", err))
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file.Name(), fileName) {
|
||||
testYamlCount++
|
||||
policyresoucePath := strings.Trim(yamlFilePath, fileName)
|
||||
bytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
sanitizederror.NewWithError("Error: failed to read file", err)
|
||||
errors = append(errors, sanitizederror.NewWithError("Error: failed to read file", err))
|
||||
continue
|
||||
}
|
||||
policyBytes, err := yaml.ToJSON(bytes)
|
||||
if err != nil {
|
||||
sanitizederror.NewWithError("failed to convert to JSON", err)
|
||||
errors = append(errors, sanitizederror.NewWithError("failed to convert to JSON", err))
|
||||
continue
|
||||
}
|
||||
if err := applyPoliciesFromPath(fs, policyBytes, valuesFile, true, policyresoucePath, rc); err != nil {
|
||||
return rc, sanitizederror.NewWithError("failed to apply test command", err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
sanitizederror.NewWithError("Error: failed to open file", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if testYamlCount == 0 {
|
||||
fmt.Printf("\n No test yamls available \n")
|
||||
}
|
||||
} else {
|
||||
path := filepath.Clean(dirPath[0])
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
err := getLocalDirTestFiles(fs, path, fileName, valuesFile, rc, testYamlCount)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if len(errors) > 0 && log.Log.V(1).Enabled() {
|
||||
fmt.Printf("ignoring errors: \n")
|
||||
for _, e := range errors {
|
||||
fmt.Printf(" %v \n", e.Error())
|
||||
}
|
||||
errors = getLocalDirTestFiles(fs, path, fileName, valuesFile, rc)
|
||||
}
|
||||
if len(errors) > 0 && log.Log.V(1).Enabled() {
|
||||
fmt.Printf("ignoring errors: \n")
|
||||
for _, e := range errors {
|
||||
fmt.Printf(" %v \n", e.Error())
|
||||
}
|
||||
}
|
||||
if rc.fail > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
if testYamlCount == 0 {
|
||||
fmt.Printf("\n No test yamls available \n")
|
||||
}
|
||||
os.Exit(0)
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func getLocalDirTestFiles(fs billy.Filesystem, path, fileName, valuesFile string, rc *resultCounts, testYamlCount int) error {
|
||||
func getLocalDirTestFiles(fs billy.Filesystem, path, fileName, valuesFile string, rc *resultCounts) []error {
|
||||
var errors []error
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %v: %v", path, err.Error())
|
||||
return []error{fmt.Errorf("failed to read %v: %v", path, err.Error())}
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
getLocalDirTestFiles(fs, filepath.Join(path, file.Name()), fileName, valuesFile, rc, testYamlCount)
|
||||
getLocalDirTestFiles(fs, filepath.Join(path, file.Name()), fileName, valuesFile, rc)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file.Name(), fileName) {
|
||||
testYamlCount++
|
||||
yamlFile, err := ioutil.ReadFile(filepath.Join(path, file.Name()))
|
||||
if err != nil {
|
||||
sanitizederror.NewWithError("unable to read yaml", err)
|
||||
errors = append(errors, sanitizederror.NewWithError("unable to read yaml", err))
|
||||
continue
|
||||
}
|
||||
valuesBytes, err := yaml.ToJSON(yamlFile)
|
||||
if err != nil {
|
||||
sanitizederror.NewWithError("failed to convert json", err)
|
||||
errors = append(errors, sanitizederror.NewWithError("failed to convert json", err))
|
||||
continue
|
||||
}
|
||||
if err := applyPoliciesFromPath(fs, valuesBytes, valuesFile, false, path, rc); err != nil {
|
||||
sanitizederror.NewWithError("failed to apply test command", err)
|
||||
errors = append(errors, sanitizederror.NewWithError(fmt.Sprintf("failed to apply test command from file %s", file.Name()), err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errors
|
||||
}
|
||||
|
||||
func buildPolicyResults(resps []*response.EngineResponse) map[string][]interface{} {
|
||||
results := make(map[string][]interface{})
|
||||
func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResults) map[string]report.PolicyReportResult {
|
||||
results := make(map[string]report.PolicyReportResult)
|
||||
infos := policyreport.GeneratePRsFromEngineResponse(resps, log.Log)
|
||||
for _, resp := range resps {
|
||||
policyName := resp.PolicyResponse.Policy
|
||||
resourceName := resp.PolicyResponse.Resource.Name
|
||||
var rules []string
|
||||
for _, rule := range resp.PolicyResponse.Rules {
|
||||
rules = append(rules, rule.Name)
|
||||
}
|
||||
result := report.PolicyReportResult{
|
||||
Policy: policyName,
|
||||
Resources: []*corev1.ObjectReference{
|
||||
{
|
||||
Name: resourceName,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range testResults {
|
||||
if test.Policy == policyName && test.Resource == resourceName {
|
||||
if !util.ContainsString(rules, test.Rule) {
|
||||
result.Status = report.StatusSkip
|
||||
}
|
||||
resultsKey := fmt.Sprintf("%s-%s-%s", test.Policy, test.Rule, test.Resource)
|
||||
if _, ok := results[resultsKey]; !ok {
|
||||
results[resultsKey] = result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, info := range infos {
|
||||
for _, infoResult := range info.Results {
|
||||
for _, rule := range infoResult.Rules {
|
||||
if rule.Type != utils.Validation.String() {
|
||||
continue
|
||||
}
|
||||
result := report.PolicyReportResult{
|
||||
Policy: info.PolicyName,
|
||||
Resources: []*corev1.ObjectReference{
|
||||
{
|
||||
Name: infoResult.Resource.Name,
|
||||
},
|
||||
},
|
||||
var result report.PolicyReportResult
|
||||
resultsKey := fmt.Sprintf("%s-%s-%s", info.PolicyName, rule.Name, infoResult.Resource.Name)
|
||||
if val, ok := results[resultsKey]; ok {
|
||||
result = val
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
result.Rule = rule.Name
|
||||
result.Status = report.PolicyStatus(rule.Check)
|
||||
results[rule.Name] = append(results[rule.Name], result)
|
||||
results[resultsKey] = result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,6 +292,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
|||
var dClient *client.Client
|
||||
values := &Test{}
|
||||
var variablesString string
|
||||
store.SetMock(true)
|
||||
|
||||
if err := json.Unmarshal(policyBytes, values); err != nil {
|
||||
return sanitizederror.NewWithError("failed to decode yaml", err)
|
||||
|
@ -333,6 +357,18 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
|||
continue
|
||||
}
|
||||
for _, resource := range resources {
|
||||
var resourcePolicy string
|
||||
for polName, values := range valuesMap {
|
||||
for resName := range values {
|
||||
if resName == resource.GetName() {
|
||||
resourcePolicy = polName
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(valuesMap) != 0 && resourcePolicy != policy.GetName() {
|
||||
log.Log.V(3).Info(fmt.Sprintf("Skipping resource, policy names do not match %s != %s", resourcePolicy, policy.GetName()))
|
||||
continue
|
||||
}
|
||||
thisPolicyResourceValues := make(map[string]string)
|
||||
if len(valuesMap[policy.GetName()]) != 0 && !reflect.DeepEqual(valuesMap[policy.GetName()][resource.GetName()], Resource{}) {
|
||||
thisPolicyResourceValues = valuesMap[policy.GetName()][resource.GetName()].Values
|
||||
|
@ -349,7 +385,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
|||
validateEngineResponses = append(validateEngineResponses, validateErs)
|
||||
}
|
||||
}
|
||||
resultsMap := buildPolicyResults(validateEngineResponses)
|
||||
resultsMap := buildPolicyResults(validateEngineResponses, values.Results)
|
||||
resultErr := printTestResult(resultsMap, values.Results, rc)
|
||||
if resultErr != nil {
|
||||
return sanitizederror.NewWithError("Unable to genrate result. Error:", resultErr)
|
||||
|
@ -357,40 +393,38 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
|||
return
|
||||
}
|
||||
|
||||
func printTestResult(resps map[string][]interface{}, testResults []TestResults, rc *resultCounts) error {
|
||||
func printTestResult(resps map[string]report.PolicyReportResult, testResults []TestResults, rc *resultCounts) error {
|
||||
printer := tableprinter.New(os.Stdout)
|
||||
table := []*Table{}
|
||||
boldGreen := color.New(color.FgGreen).Add(color.Bold)
|
||||
boldRed := color.New(color.FgRed).Add(color.Bold)
|
||||
boldYellow := color.New(color.FgYellow).Add(color.Bold)
|
||||
boldFgCyan := color.New(color.FgCyan).Add(color.Bold)
|
||||
for i, v := range testResults {
|
||||
res := new(Table)
|
||||
res.ID = i + 1
|
||||
res.Resource = boldFgCyan.Sprintf(v.Resource) + " with " + boldFgCyan.Sprintf(v.Policy) + "/" + boldFgCyan.Sprintf(v.Rule)
|
||||
n := resps[v.Rule]
|
||||
data, _ := json.Marshal(n)
|
||||
valuesBytes, err := yaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return sanitizederror.NewWithError("failed to convert json", err)
|
||||
resultKey := fmt.Sprintf("%s-%s-%s", v.Policy, v.Rule, v.Resource)
|
||||
var testRes report.PolicyReportResult
|
||||
if val, ok := resps[resultKey]; ok {
|
||||
testRes = val
|
||||
} else {
|
||||
res.Result = boldYellow.Sprintf("Not found")
|
||||
rc.fail++
|
||||
table = append(table, res)
|
||||
continue
|
||||
}
|
||||
var r []ReportResult
|
||||
json.Unmarshal(valuesBytes, &r)
|
||||
res.Result = boldRed.Sprintf("Fail")
|
||||
if len(r) != 0 {
|
||||
var resource TestResults
|
||||
for _, testRes := range r {
|
||||
if testRes.Resources[0].Name == v.Resource {
|
||||
resource.Policy = testRes.Policy
|
||||
resource.Rule = testRes.Rule
|
||||
resource.Status = testRes.Status
|
||||
resource.Resource = testRes.Resources[0].Name
|
||||
if v == resource {
|
||||
res.Result = "Pass"
|
||||
rc.pass++
|
||||
} else {
|
||||
rc.fail++
|
||||
}
|
||||
}
|
||||
if testRes.Status == v.Status {
|
||||
if testRes.Status == report.StatusSkip {
|
||||
res.Result = boldGreen.Sprintf("Skip")
|
||||
rc.skip++
|
||||
} else {
|
||||
res.Result = boldGreen.Sprintf("Pass")
|
||||
rc.pass++
|
||||
}
|
||||
} else {
|
||||
res.Result = boldRed.Sprintf("Fail")
|
||||
rc.fail++
|
||||
}
|
||||
table = append(table, res)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ func Test_validateUsingPolicyCRD(t *testing.T) {
|
|||
type TestCase struct {
|
||||
rawPolicy []byte
|
||||
errorDetail string
|
||||
detail string
|
||||
}
|
||||
|
||||
testcases := []TestCase{
|
||||
|
@ -57,7 +58,9 @@ func Test_validateUsingPolicyCRD(t *testing.T) {
|
|||
}
|
||||
`),
|
||||
errorDetail: "spec.rules.name in body should be at most 63 chars long",
|
||||
detail: "Test: char count for rule name",
|
||||
},
|
||||
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
|
@ -92,6 +95,271 @@ func Test_validateUsingPolicyCRD(t *testing.T) {
|
|||
}
|
||||
`),
|
||||
errorDetail: "",
|
||||
detail: "Test: basic vaild policy",
|
||||
},
|
||||
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "disallow-singleton"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "audit",
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-replicas",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"annotations": {
|
||||
"singleton": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Replicasets require at least 2 replicas.",
|
||||
"pattern": {
|
||||
"spec": {
|
||||
"replicas": ">1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
errorDetail: "",
|
||||
detail: "Test: schema validation for spec.rules.match.resources.annotations",
|
||||
},
|
||||
|
||||
{
|
||||
rawPolicy: []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "disallow-singleton"
|
||||
},
|
||||
"spec": {
|
||||
"validationFailureAction": "audit",
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-replicas",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": {
|
||||
"resources": {
|
||||
"annotations": {
|
||||
"singleton": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Replicasets require at least 2 replicas.",
|
||||
"pattern": {
|
||||
"spec": {
|
||||
"replicas": ">1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
errorDetail: "",
|
||||
detail: "Test: schema validation for spec.rules.exclude.resources.annotations",
|
||||
},
|
||||
|
||||
{
|
||||
rawPolicy: []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": {
|
||||
"matchLabels": {
|
||||
"app-namespace": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The Pod must end with -nginx",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"name": "*-nginx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
errorDetail: "",
|
||||
detail: "Test: schema validation for spec.rules.match.resources.namespaceSelector.matchLabels",
|
||||
},
|
||||
|
||||
{
|
||||
rawPolicy: []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"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": {
|
||||
"resources": {
|
||||
"namespaceSelector": {
|
||||
"matchLabels": {
|
||||
"app-namespace": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The Pod must end with -nginx",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"name": "*-nginx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
errorDetail: "",
|
||||
detail: "Test: schema validation for spec.rules.exclude.resources.namespaceSelector.matchLabels",
|
||||
},
|
||||
|
||||
{
|
||||
rawPolicy: []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"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app-namespace": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The Pod must end with -nginx",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"name": "*-nginx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
errorDetail: "",
|
||||
detail: "Test: schema validation for spec.rules.match.resources.selector.matchLabels",
|
||||
},
|
||||
|
||||
{
|
||||
rawPolicy: []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"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": {
|
||||
"resources": {
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app-namespace": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "The Pod must end with -nginx",
|
||||
"pattern": {
|
||||
"metadata": {
|
||||
"name": "*-nginx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`),
|
||||
errorDetail: "",
|
||||
detail: "Test: schema validation for spec.rules.exclude.resources.selector.matchLabels",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -104,7 +372,7 @@ func Test_validateUsingPolicyCRD(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
|
||||
_, errorList := validatePolicyAccordingToPolicyCRD(&policy, v1crd)
|
||||
fmt.Println("errorList: ", errorList)
|
||||
fmt.Println(tc.detail)
|
||||
for _, e := range errorList {
|
||||
assert.Assert(t, tc.errorDetail == e.Detail)
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ func ConvertPolicyToClusterPolicy(nsPolicies *kyverno.Policy) *kyverno.ClusterPo
|
|||
return &cpol
|
||||
}
|
||||
|
||||
func parseNamespacedPolicy(key string) (string, string, bool) {
|
||||
func ParseNamespacedPolicy(key string) (string, string, bool) {
|
||||
namespace := ""
|
||||
index := strings.Index(key, "/")
|
||||
if index != -1 {
|
||||
|
|
|
@ -45,7 +45,11 @@ func (pc *PolicyController) processExistingResources(policy *kyverno.ClusterPoli
|
|||
|
||||
namespaces := pc.getNamespacesForRule(&rule, logger.WithValues("kind", k))
|
||||
for _, ns := range namespaces {
|
||||
pc.applyAndReportPerNamespace(policy, k, ns, rule, logger.WithValues("kind", k).WithValues("ns", ns))
|
||||
// for kind: Policy, consider only the namespace which the policy belongs to.
|
||||
// for kind: ClusterPolicy, consider all the namespaces.
|
||||
if policy.Namespace == ns || policy.Namespace == "" {
|
||||
pc.applyAndReportPerNamespace(policy, k, ns, rule, logger.WithValues("kind", k).WithValues("ns", ns))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -690,7 +690,13 @@ func validateAPICall(entry kyverno.ContextEntry) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if entry.APICall.JMESPath != "" {
|
||||
// If JMESPath contains variables, the validation will fail because it's not possible to infer which value
|
||||
// will be inserted by the variable
|
||||
// Skip validation if a variable is detected
|
||||
|
||||
jmesPath := variables.ReplaceAllVars(entry.APICall.JMESPath, func(s string) string { return "kyvernojmespathvariable" })
|
||||
|
||||
if !strings.Contains(jmesPath, "kyvernojmespathvariable") && entry.APICall.JMESPath != "" {
|
||||
if _, err := jmespath.NewParser().Parse(entry.APICall.JMESPath); err != nil {
|
||||
return fmt.Errorf("failed to parse JMESPath %s: %v", entry.APICall.JMESPath, err)
|
||||
}
|
||||
|
|
|
@ -474,7 +474,7 @@ func (pc *PolicyController) syncPolicy(key string) error {
|
|||
}
|
||||
|
||||
func (pc *PolicyController) getPolicy(key string) (policy *kyverno.ClusterPolicy, err error) {
|
||||
namespace, key, isNamespacedPolicy := parseNamespacedPolicy(key)
|
||||
namespace, key, isNamespacedPolicy := ParseNamespacedPolicy(key)
|
||||
if !isNamespacedPolicy {
|
||||
return pc.pLister.Get(key)
|
||||
}
|
||||
|
|
|
@ -1286,6 +1286,7 @@ func Test_Validate_Kind(t *testing.T) {
|
|||
err = Validate(policy, nil, true, openAPIController)
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
||||
func Test_checkAutoGenRules(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
@ -1323,3 +1324,48 @@ func Test_checkAutoGenRules(t *testing.T) {
|
|||
assert.Equal(t, test.expectedResult, res, fmt.Sprintf("test %s failed", test.name))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_ApiCall(t *testing.T) {
|
||||
testCases := []struct {
|
||||
resource kyverno.ContextEntry
|
||||
expectedResult interface{}
|
||||
}{
|
||||
{
|
||||
resource: kyverno.ContextEntry{
|
||||
APICall: &kyverno.APICall{
|
||||
URLPath: "/apis/networking.k8s.io/v1/namespaces/{{request.namespace}}/networkpolicies",
|
||||
JMESPath: "",
|
||||
},
|
||||
},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
resource: kyverno.ContextEntry{
|
||||
APICall: &kyverno.APICall{
|
||||
URLPath: "/apis/networking.k8s.io/v1/namespaces/{{request.namespace}}/networkpolicies",
|
||||
JMESPath: "items[",
|
||||
},
|
||||
},
|
||||
expectedResult: "failed to parse JMESPath items[: SyntaxError: Expected tStar, received: tEOF",
|
||||
},
|
||||
{
|
||||
resource: kyverno.ContextEntry{
|
||||
APICall: &kyverno.APICall{
|
||||
URLPath: "/apis/networking.k8s.io/v1/namespaces/{{request.namespace}}/networkpolicies",
|
||||
JMESPath: "items[{{request.namespace}}",
|
||||
},
|
||||
},
|
||||
expectedResult: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
err := validateAPICall(testCase.resource)
|
||||
|
||||
if err == nil {
|
||||
assert.Equal(t, err, testCase.expectedResult)
|
||||
} else {
|
||||
assert.Equal(t, err.Error(), testCase.expectedResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,18 @@ import (
|
|||
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
|
||||
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
||||
)
|
||||
|
||||
type pMap struct {
|
||||
sync.RWMutex
|
||||
// dataMap field stores ClusterPolicies
|
||||
dataMap map[PolicyType][]*kyverno.ClusterPolicy
|
||||
// nsDataMap field stores Namespaced Policies for each namespaces.
|
||||
// The Policy is converted internally to ClusterPolicy and stored as a ClusterPolicy
|
||||
// Since both the policy use same type (i.e. Policy), Both policies can be differentiated based on
|
||||
// "Kind" or "namespace". When the Policy is converted it will retain the value of kind as "Policy".
|
||||
// Cluster policy will be having namespace as Blank (""), but Policy will always be having namespace field and "default" value by default
|
||||
nsDataMap map[string]map[PolicyType][]*kyverno.ClusterPolicy
|
||||
|
||||
// kindDataMap field stores names of ClusterPolicies and Namespaced Policies.
|
||||
// Since both the policy name use same type (i.e. string), Both policies can be differentiated based on
|
||||
// "namespace". namespace policy get stored with policy namespace with policy name"
|
||||
// kindDataMap {"kind": {{"policytype" : {"policyName","nsname/policyName}}},"kind2": {{"policytype" : {"nsname/policyName" }}}}
|
||||
kindDataMap map[string]map[PolicyType][]string
|
||||
|
||||
// nameCacheMap stores the names of all existing policies in dataMap
|
||||
// Policy names are stored as <namespace>/<name>
|
||||
|
@ -27,17 +27,24 @@ type pMap struct {
|
|||
type policyCache struct {
|
||||
pMap
|
||||
logr.Logger
|
||||
// list/get cluster policy resource
|
||||
pLister kyvernolister.ClusterPolicyLister
|
||||
|
||||
// npLister can list/get namespace policy from the shared informer's store
|
||||
npLister kyvernolister.PolicyLister
|
||||
}
|
||||
|
||||
// Interface ...
|
||||
// Interface get method use for to get policy names and mostly use to test cache testcases
|
||||
type Interface interface {
|
||||
Add(policy *kyverno.ClusterPolicy)
|
||||
Remove(policy *kyverno.ClusterPolicy)
|
||||
Get(pkey PolicyType, nspace *string) []*kyverno.ClusterPolicy
|
||||
GetPolicyObject(pkey PolicyType, kind string, nspace string) []*kyverno.ClusterPolicy
|
||||
get(pkey PolicyType, kind string, nspace string) []string
|
||||
}
|
||||
|
||||
// newPolicyCache ...
|
||||
func newPolicyCache(log logr.Logger) Interface {
|
||||
func newPolicyCache(log logr.Logger, pLister kyvernolister.ClusterPolicyLister, npLister kyvernolister.PolicyLister) Interface {
|
||||
namesCache := map[PolicyType]map[string]bool{
|
||||
Mutate: make(map[string]bool),
|
||||
ValidateEnforce: make(map[string]bool),
|
||||
|
@ -47,24 +54,27 @@ func newPolicyCache(log logr.Logger) Interface {
|
|||
|
||||
return &policyCache{
|
||||
pMap{
|
||||
dataMap: make(map[PolicyType][]*kyverno.ClusterPolicy),
|
||||
nsDataMap: make(map[string]map[PolicyType][]*kyverno.ClusterPolicy),
|
||||
nameCacheMap: namesCache,
|
||||
kindDataMap: make(map[string]map[PolicyType][]string),
|
||||
},
|
||||
log,
|
||||
pLister,
|
||||
npLister,
|
||||
}
|
||||
}
|
||||
|
||||
// Add a policy to cache
|
||||
func (pc *policyCache) Add(policy *kyverno.ClusterPolicy) {
|
||||
pc.pMap.add(policy)
|
||||
|
||||
pc.Logger.V(4).Info("policy is added to cache", "name", policy.GetName())
|
||||
}
|
||||
|
||||
// Get the list of matched policies
|
||||
func (pc *policyCache) Get(pkey PolicyType, nspace *string) []*kyverno.ClusterPolicy {
|
||||
return pc.pMap.get(pkey, nspace)
|
||||
func (pc *policyCache) get(pkey PolicyType, kind, nspace string) []string {
|
||||
return pc.pMap.get(pkey, kind, nspace)
|
||||
}
|
||||
func (pc *policyCache) GetPolicyObject(pkey PolicyType, kind, nspace string) []*kyverno.ClusterPolicy {
|
||||
return pc.getPolicyObject(pkey, kind, nspace)
|
||||
}
|
||||
|
||||
// Remove a policy from cache
|
||||
|
@ -84,136 +94,121 @@ func (m *pMap) add(policy *kyverno.ClusterPolicy) {
|
|||
generateMap := m.nameCacheMap[Generate]
|
||||
var pName = policy.GetName()
|
||||
pSpace := policy.GetNamespace()
|
||||
isNamespacedPolicy := false
|
||||
if pSpace != "" {
|
||||
pName = pSpace + "/" + pName
|
||||
isNamespacedPolicy = true
|
||||
// Initialize Namespace Cache Map
|
||||
_, ok := m.nsDataMap[policy.GetNamespace()]
|
||||
if !ok {
|
||||
m.nsDataMap[policy.GetNamespace()] = make(map[PolicyType][]*kyverno.ClusterPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if rule.HasMutate() {
|
||||
if !mutateMap[pName] {
|
||||
mutateMap[pName] = true
|
||||
if isNamespacedPolicy {
|
||||
mutatePolicy := m.nsDataMap[policy.GetNamespace()][Mutate]
|
||||
m.nsDataMap[policy.GetNamespace()][Mutate] = append(mutatePolicy, policy)
|
||||
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
_, ok := m.kindDataMap[kind]
|
||||
if !ok {
|
||||
m.kindDataMap[kind] = make(map[PolicyType][]string)
|
||||
}
|
||||
|
||||
if rule.HasMutate() {
|
||||
if !mutateMap[kind+"/"+pName] {
|
||||
mutateMap[kind+"/"+pName] = true
|
||||
mutatePolicy := m.kindDataMap[kind][Mutate]
|
||||
m.kindDataMap[kind][Mutate] = append(mutatePolicy, pName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if rule.HasValidate() {
|
||||
if enforcePolicy {
|
||||
if !validateEnforceMap[kind+"/"+pName] {
|
||||
validateEnforceMap[kind+"/"+pName] = true
|
||||
validatePolicy := m.kindDataMap[kind][ValidateEnforce]
|
||||
m.kindDataMap[kind][ValidateEnforce] = append(validatePolicy, pName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
mutatePolicy := m.dataMap[Mutate]
|
||||
m.dataMap[Mutate] = append(mutatePolicy, policy)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if rule.HasValidate() {
|
||||
if enforcePolicy {
|
||||
if !validateEnforceMap[pName] {
|
||||
validateEnforceMap[pName] = true
|
||||
if isNamespacedPolicy {
|
||||
validatePolicy := m.nsDataMap[policy.GetNamespace()][ValidateEnforce]
|
||||
m.nsDataMap[policy.GetNamespace()][ValidateEnforce] = append(validatePolicy, policy)
|
||||
continue
|
||||
}
|
||||
validatePolicy := m.dataMap[ValidateEnforce]
|
||||
m.dataMap[ValidateEnforce] = append(validatePolicy, policy)
|
||||
// ValidateAudit
|
||||
if !validateAuditMap[kind+"/"+pName] {
|
||||
validateAuditMap[kind+"/"+pName] = true
|
||||
validatePolicy := m.kindDataMap[kind][ValidateAudit]
|
||||
m.kindDataMap[kind][ValidateAudit] = append(validatePolicy, pName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// ValidateAudit
|
||||
if !validateAuditMap[pName] {
|
||||
validateAuditMap[pName] = true
|
||||
if isNamespacedPolicy {
|
||||
validatePolicy := m.nsDataMap[policy.GetNamespace()][ValidateAudit]
|
||||
m.nsDataMap[policy.GetNamespace()][ValidateAudit] = append(validatePolicy, policy)
|
||||
continue
|
||||
if rule.HasGenerate() {
|
||||
if !generateMap[kind+"/"+pName] {
|
||||
generateMap[kind+"/"+pName] = true
|
||||
generatePolicy := m.kindDataMap[kind][Generate]
|
||||
m.kindDataMap[kind][Generate] = append(generatePolicy, pName)
|
||||
}
|
||||
validatePolicy := m.dataMap[ValidateAudit]
|
||||
m.dataMap[ValidateAudit] = append(validatePolicy, policy)
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if rule.HasGenerate() {
|
||||
if !generateMap[pName] {
|
||||
generateMap[pName] = true
|
||||
if isNamespacedPolicy {
|
||||
generatePolicy := m.nsDataMap[policy.GetNamespace()][Generate]
|
||||
m.nsDataMap[policy.GetNamespace()][Generate] = append(generatePolicy, policy)
|
||||
continue
|
||||
}
|
||||
generatePolicy := m.dataMap[Generate]
|
||||
m.dataMap[Generate] = append(generatePolicy, policy)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
m.nameCacheMap[Mutate] = mutateMap
|
||||
m.nameCacheMap[ValidateEnforce] = validateEnforceMap
|
||||
m.nameCacheMap[ValidateAudit] = validateAuditMap
|
||||
m.nameCacheMap[Generate] = generateMap
|
||||
}
|
||||
|
||||
func (m *pMap) get(key PolicyType, nspace *string) []*kyverno.ClusterPolicy {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
if nspace == nil || *nspace == "" {
|
||||
return m.dataMap[key]
|
||||
func (pc *pMap) get(key PolicyType, kind, namespace string) (names []string) {
|
||||
pc.RLock()
|
||||
defer pc.RUnlock()
|
||||
for _, policyName := range pc.kindDataMap[kind][key] {
|
||||
ns, key, isNamespacedPolicy := policy2.ParseNamespacedPolicy(policyName)
|
||||
if !isNamespacedPolicy {
|
||||
names = append(names, key)
|
||||
} else {
|
||||
if ns == namespace {
|
||||
names = append(names, policyName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.nsDataMap[*nspace][key]
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func (m *pMap) remove(policy *kyverno.ClusterPolicy) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
var pName = policy.GetName()
|
||||
pSpace := policy.GetNamespace()
|
||||
isNamespacedPolicy := false
|
||||
if pSpace != "" {
|
||||
pName = pSpace + "/" + pName
|
||||
isNamespacedPolicy = true
|
||||
}
|
||||
if !isNamespacedPolicy {
|
||||
dataMap := m.dataMap
|
||||
for k, policies := range dataMap {
|
||||
|
||||
var newPolicies []*kyverno.ClusterPolicy
|
||||
for _, p := range policies {
|
||||
if p.GetName() == pName {
|
||||
continue
|
||||
}
|
||||
newPolicies = append(newPolicies, p)
|
||||
}
|
||||
|
||||
m.dataMap[k] = newPolicies
|
||||
}
|
||||
} else {
|
||||
dataMap := m.nsDataMap[pSpace]
|
||||
for k, policies := range dataMap {
|
||||
|
||||
var newPolicies []*kyverno.ClusterPolicy
|
||||
for _, p := range policies {
|
||||
if (p.GetNamespace() + "/" + p.GetName()) == pName {
|
||||
continue
|
||||
}
|
||||
newPolicies = append(newPolicies, p)
|
||||
}
|
||||
|
||||
m.nsDataMap[pSpace][k] = newPolicies
|
||||
}
|
||||
}
|
||||
|
||||
for _, nameCache := range m.nameCacheMap {
|
||||
if _, ok := nameCache[pName]; ok {
|
||||
delete(nameCache, pName)
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
dataMap := m.kindDataMap[kind]
|
||||
for policyType, policies := range dataMap {
|
||||
var newPolicies []string
|
||||
for _, p := range policies {
|
||||
if p == pName {
|
||||
continue
|
||||
}
|
||||
newPolicies = append(newPolicies, p)
|
||||
}
|
||||
m.kindDataMap[kind][policyType] = newPolicies
|
||||
}
|
||||
for _, nameCache := range m.nameCacheMap {
|
||||
if ok := nameCache[kind+"/"+pName]; ok {
|
||||
delete(nameCache, kind+"/"+pName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
func (m *policyCache) getPolicyObject(key PolicyType, kind string, nspace string) (policyObject []*kyverno.ClusterPolicy) {
|
||||
policyNames := m.pMap.get(key, kind, nspace)
|
||||
for _, policyName := range policyNames {
|
||||
var policy *kyverno.ClusterPolicy
|
||||
ns, key, isNamespacedPolicy := policy2.ParseNamespacedPolicy(policyName)
|
||||
if !isNamespacedPolicy {
|
||||
policy, _ = m.pLister.Get(key)
|
||||
} else {
|
||||
if ns == nspace {
|
||||
nspolicy, _ := m.npLister.Policies(ns).Get(key)
|
||||
policy = policy2.ConvertPolicyToClusterPolicy(nspolicy)
|
||||
}
|
||||
}
|
||||
policyObject = append(policyObject, policy)
|
||||
}
|
||||
return policyObject
|
||||
}
|
||||
|
|
|
@ -2,101 +2,151 @@ package policycache
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
|
||||
lv1 "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
|
||||
"gotest.tools/assert"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
type dummyLister struct {
|
||||
}
|
||||
|
||||
func (dl dummyLister) List(selector labels.Selector) (ret []*kyverno.ClusterPolicy, err error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (dl dummyLister) Get(name string) (*kyverno.ClusterPolicy, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (dl dummyLister) ListResources(selector labels.Selector) (ret []*kyverno.ClusterPolicy, err error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// type dymmyNsNamespace struct {}
|
||||
|
||||
type dummyNsLister struct {
|
||||
}
|
||||
|
||||
func (dl dummyNsLister) Policies(name string) lv1.PolicyNamespaceLister {
|
||||
return dummyNsLister{}
|
||||
}
|
||||
|
||||
func (dl dummyNsLister) List(selector labels.Selector) (ret []*kyverno.Policy, err error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (dl dummyNsLister) Get(name string) (*kyverno.Policy, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func Test_All(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newPolicy(t)
|
||||
|
||||
// add
|
||||
//add
|
||||
pCache.Add(policy)
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
|
||||
// get
|
||||
if len(pCache.Get(Mutate, nil)) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(pCache.Get(Mutate, nil)))
|
||||
}
|
||||
// get
|
||||
mutate := pCache.get(Mutate, kind, "")
|
||||
if len(mutate) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(mutate))
|
||||
}
|
||||
|
||||
if len(pCache.Get(ValidateEnforce, nil)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, nil)))
|
||||
}
|
||||
|
||||
if len(pCache.Get(Generate, nil)) != 1 {
|
||||
t.Errorf("expected 1 generate policy, found %v", len(pCache.Get(Generate, nil)))
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, "")
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate policy, found %v", len(validateEnforce))
|
||||
}
|
||||
generate := pCache.get(Generate, kind, "")
|
||||
if len(generate) != 1 {
|
||||
t.Errorf("expected 1 generate policy, found %v", len(generate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove
|
||||
pCache.Remove(policy)
|
||||
assert.Assert(t, len(pCache.Get(ValidateEnforce, nil)) == 0)
|
||||
kind := "pod"
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, "")
|
||||
assert.Assert(t, len(validateEnforce) == 0)
|
||||
}
|
||||
|
||||
func Test_Add_Duplicate_Policy(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newPolicy(t)
|
||||
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
|
||||
if len(pCache.Get(Mutate, nil)) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(pCache.Get(Mutate, nil)))
|
||||
}
|
||||
mutate := pCache.get(Mutate, kind, "")
|
||||
if len(mutate) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(mutate))
|
||||
}
|
||||
|
||||
if len(pCache.Get(ValidateEnforce, nil)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, nil)))
|
||||
}
|
||||
|
||||
if len(pCache.Get(Generate, nil)) != 1 {
|
||||
t.Errorf("expected 1 generate policy, found %v", len(pCache.Get(Generate, nil)))
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, "")
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate policy, found %v", len(validateEnforce))
|
||||
}
|
||||
generate := pCache.get(Generate, kind, "")
|
||||
if len(generate) != 1 {
|
||||
t.Errorf("expected 1 generate policy, found %v", len(generate))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Add_Validate_Audit(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newPolicy(t)
|
||||
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
|
||||
policy.Spec.ValidationFailureAction = "audit"
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
|
||||
if len(pCache.Get(ValidateEnforce, nil)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, nil)))
|
||||
}
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, "")
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(validateEnforce))
|
||||
}
|
||||
|
||||
if len(pCache.Get(ValidateAudit, nil)) != 1 {
|
||||
t.Errorf("expected 1 validate audit policy, found %v", len(pCache.Get(ValidateAudit, nil)))
|
||||
validateAudit := pCache.get(ValidateAudit, kind, "")
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate policy, found %v", len(validateAudit))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Add_Remove(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newPolicy(t)
|
||||
|
||||
kind := "Pod"
|
||||
pCache.Add(policy)
|
||||
if len(pCache.Get(ValidateEnforce, nil)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, nil)))
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, "")
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(validateEnforce))
|
||||
}
|
||||
|
||||
pCache.Remove(policy)
|
||||
if len(pCache.Get(ValidateEnforce, nil)) != 0 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, nil)))
|
||||
}
|
||||
|
||||
pCache.Add(policy)
|
||||
if len(pCache.Get(ValidateEnforce, nil)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, nil)))
|
||||
deletedValidateEnforce := pCache.get(ValidateEnforce, kind, "")
|
||||
if len(deletedValidateEnforce) != 0 {
|
||||
t.Errorf("expected 0 validate enforce policy, found %v", len(deletedValidateEnforce))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Remove_From_Empty_Cache(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, nil, nil)
|
||||
policy := newPolicy(t)
|
||||
|
||||
pCache.Remove(policy)
|
||||
|
@ -115,19 +165,20 @@ func newPolicy(t *testing.T) *kyverno.ClusterPolicy {
|
|||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
"Pod",
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"deny": {
|
||||
"conditions": {
|
||||
"all": [
|
||||
{
|
||||
"key": "a",
|
||||
"operator": "Equals",
|
||||
"value": "a"
|
||||
}
|
||||
"all": [
|
||||
{
|
||||
"key": "a",
|
||||
"operator": "Equals",
|
||||
"value": "a"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +210,8 @@ func newPolicy(t *testing.T) *kyverno.ClusterPolicy {
|
|||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
"Pod",
|
||||
"Namespace"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -178,7 +230,8 @@ func newPolicy(t *testing.T) *kyverno.ClusterPolicy {
|
|||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
"Namespace",
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -285,7 +338,7 @@ func newNsPolicy(t *testing.T) *kyverno.ClusterPolicy {
|
|||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Namespace"
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -316,89 +369,103 @@ func newNsPolicy(t *testing.T) *kyverno.ClusterPolicy {
|
|||
}
|
||||
|
||||
func Test_Ns_All(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newNsPolicy(t)
|
||||
|
||||
// add
|
||||
//add
|
||||
pCache.Add(policy)
|
||||
nspace := policy.GetNamespace()
|
||||
// get
|
||||
if len(pCache.Get(Mutate, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(pCache.Get(Mutate, &nspace)))
|
||||
}
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
|
||||
if len(pCache.Get(ValidateEnforce, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, &nspace)))
|
||||
}
|
||||
// get
|
||||
mutate := pCache.get(Mutate, kind, nspace)
|
||||
if len(mutate) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(mutate))
|
||||
}
|
||||
|
||||
if len(pCache.Get(Generate, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 generate policy, found %v", len(pCache.Get(Generate, &nspace)))
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, nspace)
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate policy, found %v", len(validateEnforce))
|
||||
}
|
||||
generate := pCache.get(Generate, kind, nspace)
|
||||
if len(generate) != 1 {
|
||||
t.Errorf("expected 1 generate policy, found %v", len(generate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove
|
||||
pCache.Remove(policy)
|
||||
assert.Assert(t, len(pCache.Get(ValidateEnforce, &nspace)) == 0)
|
||||
kind := "pod"
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, nspace)
|
||||
assert.Assert(t, len(validateEnforce) == 0)
|
||||
}
|
||||
|
||||
func Test_Ns_Add_Duplicate_Policy(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newNsPolicy(t)
|
||||
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
nspace := policy.GetNamespace()
|
||||
if len(pCache.Get(Mutate, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(pCache.Get(Mutate, &nspace)))
|
||||
}
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
|
||||
if len(pCache.Get(ValidateEnforce, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, &nspace)))
|
||||
}
|
||||
mutate := pCache.get(Mutate, kind, nspace)
|
||||
if len(mutate) != 1 {
|
||||
t.Errorf("expected 1 mutate policy, found %v", len(mutate))
|
||||
}
|
||||
|
||||
if len(pCache.Get(Generate, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 generate policy, found %v", len(pCache.Get(Generate, &nspace)))
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, nspace)
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate policy, found %v", len(validateEnforce))
|
||||
}
|
||||
generate := pCache.get(Generate, kind, nspace)
|
||||
if len(generate) != 1 {
|
||||
t.Errorf("expected 1 generate policy, found %v", len(generate))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Ns_Add_Validate_Audit(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newNsPolicy(t)
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
nspace := policy.GetNamespace()
|
||||
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
|
||||
policy.Spec.ValidationFailureAction = "audit"
|
||||
pCache.Add(policy)
|
||||
pCache.Add(policy)
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
|
||||
if len(pCache.Get(ValidateEnforce, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, &nspace)))
|
||||
}
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, nspace)
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate policy, found %v", len(validateEnforce))
|
||||
}
|
||||
|
||||
if len(pCache.Get(ValidateAudit, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 validate audit policy, found %v", len(pCache.Get(ValidateAudit, &nspace)))
|
||||
validateAudit := pCache.get(ValidateAudit, kind, nspace)
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate policy, found %v", len(validateAudit))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Ns_Add_Remove(t *testing.T) {
|
||||
pCache := newPolicyCache(log.Log)
|
||||
pCache := newPolicyCache(log.Log, dummyLister{}, dummyNsLister{})
|
||||
policy := newNsPolicy(t)
|
||||
|
||||
pCache.Add(policy)
|
||||
nspace := policy.GetNamespace()
|
||||
if len(pCache.Get(ValidateEnforce, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, &nspace)))
|
||||
kind := "Pod"
|
||||
pCache.Add(policy)
|
||||
validateEnforce := pCache.get(ValidateEnforce, kind, nspace)
|
||||
if len(validateEnforce) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(validateEnforce))
|
||||
}
|
||||
|
||||
pCache.Remove(policy)
|
||||
if len(pCache.Get(ValidateEnforce, &nspace)) != 0 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, &nspace)))
|
||||
}
|
||||
|
||||
pCache.Add(policy)
|
||||
if len(pCache.Get(ValidateEnforce, &nspace)) != 1 {
|
||||
t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce, &nspace)))
|
||||
deletedValidateEnforce := pCache.get(ValidateEnforce, kind, nspace)
|
||||
if len(deletedValidateEnforce) != 0 {
|
||||
t.Errorf("expected 0 validate enforce policy, found %v", len(deletedValidateEnforce))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func NewPolicyCacheController(
|
|||
log logr.Logger) *Controller {
|
||||
|
||||
pc := Controller{
|
||||
Cache: newPolicyCache(log),
|
||||
Cache: newPolicyCache(log, pInformer.Lister(), nspInformer.Lister()),
|
||||
log: log,
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ type resourceCache struct {
|
|||
log logr.Logger
|
||||
}
|
||||
|
||||
var KyvernoDefaultInformer = []string{"ConfigMap", "Secret", "Deployment", "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration"}
|
||||
var KyvernoDefaultInformer = []string{"ConfigMap", "Deployment", "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration"}
|
||||
|
||||
// NewResourceCache - initializes the ResourceCache
|
||||
func NewResourceCache(dclient *dclient.Client, dInformer dynamicinformer.DynamicSharedInformerFactory, logger logr.Logger) (ResourceCache, error) {
|
||||
|
|
|
@ -65,7 +65,7 @@ func (c *CertRenewer) InitTLSPemPair(serverIP string) (*PemPair, error) {
|
|||
logger.Info("using existing TLS key/certificate pair")
|
||||
return tlsPair, nil
|
||||
}
|
||||
} else {
|
||||
} else if err != nil {
|
||||
logger.V(3).Info("unable to find TLS pair", "reason", err.Error())
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
var ErrorsNotFound = "root CA certificate not found"
|
||||
|
||||
// ReadRootCASecret returns the RootCA from the pre-defined secret
|
||||
func ReadRootCASecret(restConfig *rest.Config, client *client.Client) (result []byte, err error) {
|
||||
certProps, err := GetTLSCertProps(restConfig)
|
||||
|
@ -33,7 +35,7 @@ func ReadRootCASecret(restConfig *rest.Config, client *client.Client) (result []
|
|||
|
||||
result = tlsca.Data[RootCAKey]
|
||||
if len(result) == 0 {
|
||||
return nil, errors.Errorf("root CA certificate not found in secret %s/%s", certProps.Namespace, tlsca.Name)
|
||||
return nil, errors.Errorf("%s in secret %s/%s", ErrorsNotFound, certProps.Namespace, tlsca.Name)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
|
|
@ -4,15 +4,16 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/event"
|
||||
"github.com/kyverno/kyverno/pkg/resourcecache"
|
||||
"github.com/kyverno/kyverno/pkg/tls"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
informerv1 "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
|
@ -41,22 +42,14 @@ type Monitor struct {
|
|||
}
|
||||
|
||||
//NewMonitor returns a new instance of webhook monitor
|
||||
func NewMonitor(resCache resourcecache.ResourceCache, log logr.Logger) *Monitor {
|
||||
func NewMonitor(nsInformer informerv1.SecretInformer, log logr.Logger) *Monitor {
|
||||
monitor := &Monitor{
|
||||
t: time.Now(),
|
||||
secretQueue: make(chan bool, 1),
|
||||
log: log,
|
||||
}
|
||||
|
||||
var err error
|
||||
secretCache, ok := resCache.GetGVRCache("Secret")
|
||||
if !ok {
|
||||
if secretCache, err = resCache.CreateGVKInformer("Secret"); err != nil {
|
||||
log.Error(err, "unable to start Secret's informer")
|
||||
}
|
||||
}
|
||||
|
||||
secretCache.GetInformer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
nsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: monitor.addSecretFunc,
|
||||
UpdateFunc: monitor.updateSecretFunc,
|
||||
})
|
||||
|
@ -80,7 +73,7 @@ func (t *Monitor) SetTime(tm time.Time) {
|
|||
}
|
||||
|
||||
func (t *Monitor) addSecretFunc(obj interface{}) {
|
||||
secret := obj.(*unstructured.Unstructured)
|
||||
secret := obj.(*v1.Secret)
|
||||
if secret.GetNamespace() != config.KyvernoNamespace {
|
||||
return
|
||||
}
|
||||
|
@ -94,8 +87,8 @@ func (t *Monitor) addSecretFunc(obj interface{}) {
|
|||
}
|
||||
|
||||
func (t *Monitor) updateSecretFunc(oldObj interface{}, newObj interface{}) {
|
||||
old := oldObj.(*unstructured.Unstructured)
|
||||
new := newObj.(*unstructured.Unstructured)
|
||||
old := oldObj.(*v1.Secret)
|
||||
new := newObj.(*v1.Secret)
|
||||
if new.GetNamespace() != config.KyvernoNamespace {
|
||||
return
|
||||
}
|
||||
|
@ -105,7 +98,7 @@ func (t *Monitor) updateSecretFunc(oldObj interface{}, newObj interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(old.UnstructuredContent()["data"], new.UnstructuredContent()["data"]) {
|
||||
if reflect.DeepEqual(old.DeepCopy().Data, new.DeepCopy().Data) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -182,7 +175,10 @@ func (t *Monitor) Run(register *Register, certRenewer *tls.CertRenewer, eventGen
|
|||
valid, err := certRenewer.ValidCert()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to validate cert")
|
||||
continue
|
||||
|
||||
if !strings.Contains(err.Error(), tls.ErrorsNotFound) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
|
@ -199,7 +195,10 @@ func (t *Monitor) Run(register *Register, certRenewer *tls.CertRenewer, eventGen
|
|||
valid, err := certRenewer.ValidCert()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to validate cert")
|
||||
continue
|
||||
|
||||
if !strings.Contains(err.Error(), tls.ErrorsNotFound) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
|
|
|
@ -308,11 +308,11 @@ func (ws *WebhookServer) ResourceMutation(request *v1beta1.AdmissionRequest) *v1
|
|||
}
|
||||
|
||||
logger.V(6).Info("received an admission request in mutating webhook")
|
||||
mutatePolicies := ws.pCache.Get(policycache.Mutate, nil)
|
||||
generatePolicies := ws.pCache.Get(policycache.Generate, nil)
|
||||
mutatePolicies := ws.pCache.GetPolicyObject(policycache.Mutate, request.Kind.Kind, "")
|
||||
generatePolicies := ws.pCache.GetPolicyObject(policycache.Generate, request.Kind.Kind, "")
|
||||
|
||||
// Get namespace policies from the cache for the requested resource namespace
|
||||
nsMutatePolicies := ws.pCache.Get(policycache.Mutate, &request.Namespace)
|
||||
nsMutatePolicies := ws.pCache.GetPolicyObject(policycache.Mutate, request.Kind.Kind, request.Namespace)
|
||||
mutatePolicies = append(mutatePolicies, nsMutatePolicies...)
|
||||
|
||||
// convert RAW to unstructured
|
||||
|
@ -395,9 +395,9 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
|
|||
|
||||
logger.V(6).Info("received an admission request in validating webhook")
|
||||
|
||||
policies := ws.pCache.Get(policycache.ValidateEnforce, nil)
|
||||
policies := ws.pCache.GetPolicyObject(policycache.ValidateEnforce, request.Kind.Kind, "")
|
||||
// Get namespace policies from the cache for the requested resource namespace
|
||||
nsPolicies := ws.pCache.Get(policycache.ValidateEnforce, &request.Namespace)
|
||||
nsPolicies := ws.pCache.GetPolicyObject(policycache.ValidateEnforce, request.Kind.Kind, request.Namespace)
|
||||
policies = append(policies, nsPolicies...)
|
||||
if len(policies) == 0 {
|
||||
// push admission request to audit handler, this won't block the admission request
|
||||
|
|
|
@ -149,9 +149,9 @@ func (h *auditHandler) process(request *v1beta1.AdmissionRequest) error {
|
|||
var err error
|
||||
|
||||
logger := h.log.WithName("process")
|
||||
policies := h.pCache.Get(policycache.ValidateAudit, nil)
|
||||
policies := h.pCache.GetPolicyObject(policycache.ValidateAudit, request.Kind.Kind, "")
|
||||
// Get namespace policies from the cache for the requested resource namespace
|
||||
nsPolicies := h.pCache.Get(policycache.ValidateAudit, &request.Namespace)
|
||||
nsPolicies := h.pCache.GetPolicyObject(policycache.ValidateAudit, request.Kind.Kind, request.Namespace)
|
||||
policies = append(policies, nsPolicies...)
|
||||
// getRoleRef only if policy has roles/clusterroles defined
|
||||
if containRBACInfo(policies) {
|
||||
|
|
39
test/cli/test-fail/missing-policy/policy.yaml
Normal file
39
test/cli/test-fail/missing-policy/policy.yaml
Normal file
|
@ -0,0 +1,39 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-latest-tag
|
||||
annotations:
|
||||
policies.kyverno.io/category: Best Practices
|
||||
policies.kyverno.io/description: >-
|
||||
The ':latest' tag is mutable and can lead to unexpected errors if the
|
||||
image changes. A best practice is to use an immutable tag that maps to
|
||||
a specific version of an application pod.
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: require-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
namespaces:
|
||||
- test
|
||||
validate:
|
||||
message: "An image tag is required."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "*:*"
|
||||
- name: validate-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
namespaces:
|
||||
- test
|
||||
validate:
|
||||
message: "Using a mutable image tag e.g. 'latest' is not allowed."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "!*:latest"
|
11
test/cli/test-fail/missing-policy/resources.yaml
Normal file
11
test/cli/test-fail/missing-policy/resources.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-ignore
|
||||
namespace: default
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
10
test/cli/test-fail/missing-policy/test.yaml
Normal file
10
test/cli/test-fail/missing-policy/test.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: test-simple
|
||||
policies:
|
||||
- policy.yaml
|
||||
resources:
|
||||
- resources.yaml
|
||||
results:
|
||||
- policy: missing
|
||||
rule: validate-image-tag
|
||||
resource: test
|
||||
status: pass
|
39
test/cli/test-fail/missing-resource/policy.yaml
Normal file
39
test/cli/test-fail/missing-resource/policy.yaml
Normal file
|
@ -0,0 +1,39 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-latest-tag
|
||||
annotations:
|
||||
policies.kyverno.io/category: Best Practices
|
||||
policies.kyverno.io/description: >-
|
||||
The ':latest' tag is mutable and can lead to unexpected errors if the
|
||||
image changes. A best practice is to use an immutable tag that maps to
|
||||
a specific version of an application pod.
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: require-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
namespaces:
|
||||
- test
|
||||
validate:
|
||||
message: "An image tag is required."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "*:*"
|
||||
- name: validate-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
namespaces:
|
||||
- test
|
||||
validate:
|
||||
message: "Using a mutable image tag e.g. 'latest' is not allowed."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "!*:latest"
|
11
test/cli/test-fail/missing-resource/resources.yaml
Normal file
11
test/cli/test-fail/missing-resource/resources.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-ignore
|
||||
namespace: default
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
10
test/cli/test-fail/missing-resource/test.yaml
Normal file
10
test/cli/test-fail/missing-resource/test.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: test-simple
|
||||
policies:
|
||||
- policy.yaml
|
||||
resources:
|
||||
- resources.yaml
|
||||
results:
|
||||
- policy: disallow-latest-tag
|
||||
rule: validate-image-tag
|
||||
resource: missing
|
||||
status: pass
|
39
test/cli/test-fail/missing-rule/policy.yaml
Normal file
39
test/cli/test-fail/missing-rule/policy.yaml
Normal file
|
@ -0,0 +1,39 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-latest-tag
|
||||
annotations:
|
||||
policies.kyverno.io/category: Best Practices
|
||||
policies.kyverno.io/description: >-
|
||||
The ':latest' tag is mutable and can lead to unexpected errors if the
|
||||
image changes. A best practice is to use an immutable tag that maps to
|
||||
a specific version of an application pod.
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: require-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
namespaces:
|
||||
- test
|
||||
validate:
|
||||
message: "An image tag is required."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "*:*"
|
||||
- name: validate-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
namespaces:
|
||||
- test
|
||||
validate:
|
||||
message: "Using a mutable image tag e.g. 'latest' is not allowed."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "!*:latest"
|
11
test/cli/test-fail/missing-rule/resources.yaml
Normal file
11
test/cli/test-fail/missing-rule/resources.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-ignore
|
||||
namespace: default
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
10
test/cli/test-fail/missing-rule/test.yaml
Normal file
10
test/cli/test-fail/missing-rule/test.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: test-simple
|
||||
policies:
|
||||
- policy.yaml
|
||||
resources:
|
||||
- resources.yaml
|
||||
results:
|
||||
- policy: disallow-latest-tag
|
||||
rule: missing
|
||||
resource: test
|
||||
status: pass
|
37
test/cli/test/simple/policy.yaml
Normal file
37
test/cli/test/simple/policy.yaml
Normal file
|
@ -0,0 +1,37 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-latest-tag
|
||||
annotations:
|
||||
policies.kyverno.io/category: Best Practices
|
||||
policies.kyverno.io/description: >-
|
||||
The ':latest' tag is mutable and can lead to unexpected errors if the
|
||||
image changes. A best practice is to use an immutable tag that maps to
|
||||
a specific version of an application pod.
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: require-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "An image tag is required."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "*:*"
|
||||
- name: validate-image-tag
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
namespaces:
|
||||
- test
|
||||
validate:
|
||||
message: "Using a mutable image tag e.g. 'latest' is not allowed."
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- image: "!*:latest"
|
59
test/cli/test/simple/resources.yaml
Normal file
59
test/cli/test/simple/resources.yaml
Normal file
|
@ -0,0 +1,59 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-require-image-tag-pass
|
||||
namespace: test
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-require-image-tag-fail
|
||||
namespace: test
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-validate-image-tag-ignore
|
||||
namespace: default
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-validate-image-tag-fail
|
||||
namespace: test
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-validate-image-tag-pass
|
||||
namespace: test
|
||||
labels:
|
||||
app: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.12
|
26
test/cli/test/simple/test.yaml
Normal file
26
test/cli/test/simple/test.yaml
Normal file
|
@ -0,0 +1,26 @@
|
|||
name: test-simple
|
||||
policies:
|
||||
- policy.yaml
|
||||
resources:
|
||||
- resources.yaml
|
||||
results:
|
||||
- policy: disallow-latest-tag
|
||||
rule: require-image-tag
|
||||
resource: test-require-image-tag-pass
|
||||
status: pass
|
||||
- policy: disallow-latest-tag
|
||||
rule: require-image-tag
|
||||
resource: test-require-image-tag-fail
|
||||
status: fail
|
||||
- policy: disallow-latest-tag
|
||||
rule: validate-image-tag
|
||||
resource: test-validate-image-tag-ignore
|
||||
status: skip
|
||||
- policy: disallow-latest-tag
|
||||
rule: validate-image-tag
|
||||
resource: test-validate-image-tag-fail
|
||||
status: fail
|
||||
- policy: disallow-latest-tag
|
||||
rule: validate-image-tag
|
||||
resource: test-validate-image-tag-pass
|
||||
status: pass
|
25
test/cli/test/variables/cm-array-example.yaml
Normal file
25
test/cli/test/variables/cm-array-example.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: cm-array-example
|
||||
spec:
|
||||
validationFailureAction: enforce
|
||||
background: false
|
||||
rules:
|
||||
- name: validate-role-annotation
|
||||
context:
|
||||
- name: roles-dictionary
|
||||
configMap:
|
||||
name: roles-dictionary
|
||||
namespace: default
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "The role {{ request.object.metadata.annotations.role }} is not in the allowed list of roles: {{ \"roles-dictionary\".data.\"allowed-roles\" }}."
|
||||
deny:
|
||||
conditions:
|
||||
- key: "{{ request.object.metadata.annotations.role }}"
|
||||
operator: NotIn
|
||||
value: "{{ \"roles-dictionary\".data.\"allowed-roles\" }}"
|
21
test/cli/test/variables/cm-variable-example.yaml
Normal file
21
test/cli/test/variables/cm-variable-example.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: cm-variable-example
|
||||
spec:
|
||||
rules:
|
||||
- name: example-configmap-lookup
|
||||
context:
|
||||
- name: dictionary
|
||||
configMap:
|
||||
name: some-config-map
|
||||
namespace: some-namespace
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
pattern:
|
||||
metadata:
|
||||
labels:
|
||||
my-environment-name: "{{dictionary.data.env}}"
|
43
test/cli/test/variables/resources.yaml
Normal file
43
test/cli/test/variables/resources.yaml
Normal file
|
@ -0,0 +1,43 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-env-test
|
||||
labels:
|
||||
my-environment-name: test
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-env-dev
|
||||
labels:
|
||||
my-environment-name: dev
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.12
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-web
|
||||
annotations:
|
||||
role: web
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-app
|
||||
annotations:
|
||||
role: app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.12
|
24
test/cli/test/variables/test.yaml
Normal file
24
test/cli/test/variables/test.yaml
Normal file
|
@ -0,0 +1,24 @@
|
|||
name: test-variables
|
||||
policies:
|
||||
- cm-variable-example.yaml
|
||||
- cm-array-example.yaml
|
||||
resources:
|
||||
- resources.yaml
|
||||
variables: variables.yaml
|
||||
results:
|
||||
- policy: cm-variable-example
|
||||
rule: example-configmap-lookup
|
||||
resource: test-env-test
|
||||
status: pass
|
||||
- policy: cm-variable-example
|
||||
rule: example-configmap-lookup
|
||||
resource: test-env-dev
|
||||
status: fail
|
||||
- policy: cm-array-example
|
||||
rule: validate-role-annotation
|
||||
resource: test-web
|
||||
status: fail
|
||||
- policy: cm-array-example
|
||||
rule: validate-role-annotation
|
||||
resource: test-app
|
||||
status: pass
|
25
test/cli/test/variables/variables.yaml
Normal file
25
test/cli/test/variables/variables.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
policies:
|
||||
- name: cm-variable-example
|
||||
rules:
|
||||
- name: example-configmap-lookup
|
||||
values:
|
||||
dictionary.data.env: test
|
||||
resources:
|
||||
- name: test-env-test
|
||||
values:
|
||||
request.object.metadata.name: test-env-test
|
||||
- name: test-env-dev
|
||||
values:
|
||||
request.object.metadata.name: test-env-dev
|
||||
- name: cm-array-example
|
||||
rules:
|
||||
- name: validate-role-annotation
|
||||
values:
|
||||
roles-dictionary.data.allowed-roles: "[\"app\",\"test\"]"
|
||||
resources:
|
||||
- name: test-web
|
||||
values:
|
||||
request.object.metadata.annotations.role: web
|
||||
- name: test-app
|
||||
values:
|
||||
request.object.metadata.annotations.role: app
|
Loading…
Add table
Reference in a new issue