1
0
Fork 0
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:
vyankatesh 2021-05-10 12:02:59 +05:30
commit 445394a442
54 changed files with 1327 additions and 452 deletions

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -1,4 +1,4 @@
Thank you for installing {{ .Chart.Name }} 😀
Thank you for installing {{ .Chart.Name }} {{ .Chart.Version }} 😀
Your release is named {{ .Release.Name }}.

View file

@ -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

View file

@ -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/
##

View file

@ -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:

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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
View file

@ -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=

View file

@ -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

View file

@ -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))

View file

@ -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(`
{

View file

@ -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) {

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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"
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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))
}
}
}
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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))
}
}

View file

@ -28,7 +28,7 @@ func NewPolicyCacheController(
log logr.Logger) *Controller {
pc := Controller{
Cache: newPolicyCache(log),
Cache: newPolicyCache(log, pInformer.Lister(), nspInformer.Lister()),
log: log,
}

View file

@ -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) {

View file

@ -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())
}

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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) {

View 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"

View 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

View 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

View 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"

View 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

View 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

View 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"

View 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

View 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

View 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"

View 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

View 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

View 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\" }}"

View 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}}"

View 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

View 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

View 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