mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-05 15:37:19 +00:00
refactor: introduce autogen package (#3316)
* refactor: pass only spec instead of whole policy when possible Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com> * refactor: introduce autogen package Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com> Co-authored-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
parent
1154612489
commit
7232de45c6
8 changed files with 1101 additions and 1064 deletions
185
pkg/autogen/autogen.go
Normal file
185
pkg/autogen/autogen.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package autogen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
)
|
||||
|
||||
// CanAutoGen checks whether the rule(s) (in policy) can be applied to Pod controllers
|
||||
// returns controllers as:
|
||||
// - "" if:
|
||||
// - name or selector is defined
|
||||
// - mixed kinds (Pod + pod controller) is defined
|
||||
// - Pod and PodControllers are not defined
|
||||
// - mutate.Patches/mutate.PatchesJSON6902/validate.deny/generate rule is defined
|
||||
// - otherwise it returns all pod controllers
|
||||
func CanAutoGen(spec *kyverno.Spec, log logr.Logger) (applyAutoGen bool, controllers string) {
|
||||
var needAutogen bool
|
||||
for _, rule := range spec.Rules {
|
||||
match := rule.MatchResources
|
||||
exclude := rule.ExcludeResources
|
||||
|
||||
if match.ResourceDescription.Name != "" || match.ResourceDescription.Selector != nil || match.ResourceDescription.Annotations != nil ||
|
||||
exclude.ResourceDescription.Name != "" || exclude.ResourceDescription.Selector != nil || exclude.ResourceDescription.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in resource description may not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if isKindOtherthanPod(match.Kinds) || isKindOtherthanPod(exclude.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
needAutogen = hasAutogenKinds(match.Kinds) || hasAutogenKinds(exclude.Kinds)
|
||||
|
||||
for _, value := range match.Any {
|
||||
if isKindOtherthanPod(value.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
if !needAutogen {
|
||||
needAutogen = hasAutogenKinds(value.Kinds)
|
||||
}
|
||||
if value.Name != "" || value.Selector != nil || value.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in match any block is not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
for _, value := range match.All {
|
||||
if isKindOtherthanPod(value.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
if !needAutogen {
|
||||
needAutogen = hasAutogenKinds(value.Kinds)
|
||||
}
|
||||
if value.Name != "" || value.Selector != nil || value.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in match all block is not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
for _, value := range exclude.Any {
|
||||
if isKindOtherthanPod(value.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
if !needAutogen {
|
||||
needAutogen = hasAutogenKinds(value.Kinds)
|
||||
}
|
||||
if value.Name != "" || value.Selector != nil || value.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in exclude any block is not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
for _, value := range exclude.All {
|
||||
if isKindOtherthanPod(value.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
if !needAutogen {
|
||||
needAutogen = hasAutogenKinds(value.Kinds)
|
||||
}
|
||||
if value.Name != "" || value.Selector != nil || value.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in exclud all block is not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Mutation.PatchesJSON6902 != "" || rule.HasGenerate() {
|
||||
return false, "none"
|
||||
}
|
||||
}
|
||||
|
||||
if !needAutogen {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, engine.PodControllers
|
||||
}
|
||||
|
||||
// podControllersKey annotation could be:
|
||||
// scenario A: not exist, set default to "all", which generates on all pod controllers
|
||||
// - if name / selector exist in resource description -> skip
|
||||
// as these fields may not be applicable to pod controllers
|
||||
// scenario B: "none", user explicitly disable this feature -> skip
|
||||
// scenario C: some certain controllers that user set -> generate on defined controllers
|
||||
// copy entire match / exclude block, it's users' responsibility to
|
||||
// make sure all fields are applicable to pod controllers
|
||||
|
||||
// GenerateRulePatches generates rule for podControllers based on scenario A and C
|
||||
func GenerateRulePatches(spec *kyverno.Spec, controllers string, log logr.Logger) (rulePatches [][]byte, errs []error) {
|
||||
insertIdx := len(spec.Rules)
|
||||
|
||||
ruleMap := createRuleMap(spec.Rules)
|
||||
var ruleIndex = make(map[string]int)
|
||||
for index, rule := range spec.Rules {
|
||||
ruleIndex[rule.Name] = index
|
||||
}
|
||||
|
||||
for _, rule := range spec.Rules {
|
||||
patchPostion := insertIdx
|
||||
convertToPatches := func(genRule kyvernoRule, patchPostion int) []byte {
|
||||
operation := "add"
|
||||
if existingAutoGenRule, alreadyExists := ruleMap[genRule.Name]; alreadyExists {
|
||||
existingAutoGenRuleRaw, _ := json.Marshal(existingAutoGenRule)
|
||||
genRuleRaw, _ := json.Marshal(genRule)
|
||||
|
||||
if string(existingAutoGenRuleRaw) == string(genRuleRaw) {
|
||||
return nil
|
||||
}
|
||||
operation = "replace"
|
||||
patchPostion = ruleIndex[genRule.Name]
|
||||
}
|
||||
|
||||
// generate patch bytes
|
||||
jsonPatch := struct {
|
||||
Path string `json:"path"`
|
||||
Op string `json:"op"`
|
||||
Value interface{} `json:"value"`
|
||||
}{
|
||||
fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)),
|
||||
operation,
|
||||
genRule,
|
||||
}
|
||||
pbytes, err := json.Marshal(jsonPatch)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// check the patch
|
||||
if _, err := jsonpatch.DecodePatch([]byte("[" + string(pbytes) + "]")); err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return pbytes
|
||||
}
|
||||
|
||||
// handle all other controllers other than CronJob
|
||||
genRule := generateRuleForControllers(rule, stripCronJob(controllers), log)
|
||||
if !reflect.DeepEqual(genRule, kyvernoRule{}) {
|
||||
pbytes := convertToPatches(genRule, patchPostion)
|
||||
pbytes = updateGenRuleByte(pbytes, "Pod", genRule)
|
||||
if pbytes != nil {
|
||||
rulePatches = append(rulePatches, pbytes)
|
||||
}
|
||||
insertIdx++
|
||||
patchPostion = insertIdx
|
||||
}
|
||||
|
||||
// handle CronJob, it appends an additional rule
|
||||
genRule = generateCronJobRule(rule, controllers, log)
|
||||
if !reflect.DeepEqual(genRule, kyvernoRule{}) {
|
||||
pbytes := convertToPatches(genRule, patchPostion)
|
||||
pbytes = updateGenRuleByte(pbytes, "Cronjob", genRule)
|
||||
if pbytes != nil {
|
||||
rulePatches = append(rulePatches, pbytes)
|
||||
}
|
||||
insertIdx++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
416
pkg/autogen/autogen_test.go
Normal file
416
pkg/autogen/autogen_test.go
Normal file
|
@ -0,0 +1,416 @@
|
|||
package autogen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"gotest.tools/assert"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
func Test_getControllers(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
policy []byte
|
||||
expectedControllers string
|
||||
}{
|
||||
{
|
||||
name: "rule-with-match-name",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["Namespace"],"name":"*"}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-match-selector",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"],"selector":{"matchLabels":{"foo":"bar"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-exclude-name",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"test"}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-exclude-selector",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"selector":{"matchLabels":{"foo":"bar"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-deny",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","deny":{"conditions":[{"key":"{{request.object.metadata.labels.foo}}","operator":"Equals","value":"bar"}]}}}]}}`),
|
||||
expectedControllers: engine.PodControllers,
|
||||
},
|
||||
|
||||
{
|
||||
name: "rule-with-match-mixed-kinds-pod-podcontrollers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"preconditions":{"any":[{"key":"{{request.operation}}","operator":"Equals","value":"CREATE"}]},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-exclude-mixed-kinds-pod-podcontrollers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-match-kinds-pod-only",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","pattern":{"metadata":{"labels":{"foo":"bar"}}}}}]}}`),
|
||||
expectedControllers: engine.PodControllers,
|
||||
},
|
||||
{
|
||||
name: "rule-with-exclude-kinds-pod-only",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"kinds":["Pod"],"namespaces":["test"]}},"validate":{"message":"testpolicy","pattern":{"metadata":{"labels":{"foo":"bar"}}}}}]}}`),
|
||||
expectedControllers: engine.PodControllers,
|
||||
},
|
||||
{
|
||||
name: "rule-with-mutate-patches",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["Pod"]}},"mutate":{"patchesJson6902":"-op:add\npath:/spec/containers/0/env/-1\nvalue:{\"name\":\"SERVICE\",\"value\":{{request.object.spec.template.metadata.labels.app}}}"}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-generate",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"add-networkpolicy"},"spec":{"rules":[{"name":"default-deny-ingress","match":{"resources":{"kinds":["Namespace"],"name":"*"}},"exclude":{"resources":{"namespaces":["kube-system","default","kube-public","kyverno"]}},"generate":{"kind":"NetworkPolicy","name":"default-deny-ingress","namespace":"{{request.object.metadata.name}}","synchronize":true,"data":{"spec":{"podSelector":{},"policyTypes":["Ingress"]}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-predefined-invalid-controllers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"DaemonSet,Deployment,StatefulSet","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-predefined-valid-controllers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-only-predefined-valid-controllers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Namespace"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
var policy kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(test.policy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
applyAutoGen, controllers := CanAutoGen(&policy.Spec, log.Log)
|
||||
if !applyAutoGen {
|
||||
controllers = "none"
|
||||
}
|
||||
assert.Equal(t, test.expectedControllers, controllers, fmt.Sprintf("test %s failed", test.name))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Any(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].MatchResources.Any = kyverno.ResourceFilters{
|
||||
{
|
||||
ResourceDescription: kyverno.ResourceDescription{
|
||||
Kinds: []string{"Pod"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
fmt.Println("utils.JoinPatches(patches)erterter", string(utils.JoinPatches(rulePatches)))
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"any":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_All(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].MatchResources.All = kyverno.ResourceFilters{
|
||||
{
|
||||
ResourceDescription: kyverno.ResourceDescription{
|
||||
Kinds: []string{"Pod"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"all":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"all":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Exclude(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].ExcludeResources.Namespaces = []string{"fake-namespce"}
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CronJobOnly(t *testing.T) {
|
||||
|
||||
controllers := engine.PodControllerCronJob
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.SetAnnotations(map[string]string{
|
||||
engine.PodControllersAnnotation: controllers,
|
||||
})
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, controllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, rulePatches, expectedPatches)
|
||||
}
|
||||
|
||||
func Test_ForEachPod(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/policy/mutate/policy_mutate_pod_foreach_with_context.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].ExcludeResources.Namespaces = []string{"fake-namespce"}
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-resolve-image-containers","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"preconditions":{"all":[{"key":"{{request.operation}}","operator":"In","value":["CREATE","UPDATE"]}]},"mutate":{"foreach":[{"list":"request.object.spec.template.spec.containers","context":[{"name":"dictionary","configMap":{"name":"some-config-map","namespace":"some-namespace"}}],"patchStrategicMerge":{"spec":{"template":{"spec":{"containers":[{"image":"{{ dictionary.data.image }}","name":"{{ element.name }}"}]}}}}}]}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-resolve-image-containers","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"preconditions":{"all":[{"key":"{{request.operation}}","operator":"In","value":["CREATE","UPDATE"]}]},"mutate":{"foreach":[{"list":"request.object.spec.jobTemplate.spec.template.spec.containers","context":[{"name":"dictionary","configMap":{"name":"some-config-map","namespace":"some-namespace"}}],"patchStrategicMerge":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"containers":[{"image":"{{ dictionary.data.image }}","name":"{{ element.name }}"}]}}}}}}}]}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CronJob_hasExclude(t *testing.T) {
|
||||
|
||||
controllers := engine.PodControllerCronJob
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.SetAnnotations(map[string]string{
|
||||
engine.PodControllersAnnotation: controllers,
|
||||
})
|
||||
|
||||
rule := policy.Spec.Rules[0].DeepCopy()
|
||||
rule.ExcludeResources.Kinds = []string{"Pod"}
|
||||
rule.ExcludeResources.Namespaces = []string{"test"}
|
||||
policy.Spec.Rules[0] = *rule
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, controllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"kinds":["CronJob"],"namespaces":["test"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, rulePatches, expectedPatches)
|
||||
}
|
||||
|
||||
func Test_CronJobAndDeployment(t *testing.T) {
|
||||
controllers := strings.Join([]string{engine.PodControllerCronJob, "Deployment"}, ",")
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.SetAnnotations(map[string]string{
|
||||
engine.PodControllersAnnotation: controllers,
|
||||
})
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, controllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"resources":{"kinds":["Deployment"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, rulePatches, expectedPatches)
|
||||
}
|
||||
|
||||
func Test_UpdateVariablePath(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/select-secrets.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-select-secrets-from-volumes","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"context":[{"name":"volsecret","apiCall":{"urlPath":"/api/v1/namespaces/{{request.object.spec.template.metadata.namespace}}/secrets/{{request.object.spec.template.spec.volumes[0].secret.secretName}}","jmesPath":"metadata.labels.foo"}}],"preconditions":[{"key":"{{ request.operation }}","operator":"Equals","value":"CREATE"}],"validate":{"message":"The Secret named {{request.object.spec.template.spec.volumes[0].secret.secretName}} is restricted and may not be used.","pattern":{"spec":{"template":{"spec":{"containers":[{"image":"registry.domain.com/*"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-select-secrets-from-volumes","match":{"resources":{"kinds":["CronJob"]}},"context":[{"name":"volsecret","apiCall":{"urlPath":"/api/v1/namespaces/{{request.object.spec.template.metadata.namespace}}/secrets/{{request.object.spec.jobTemplate.spec.template.spec.volumes[0].secret.secretName}}","jmesPath":"metadata.labels.foo"}}],"preconditions":[{"key":"{{ request.operation }}","operator":"Equals","value":"CREATE"}],"validate":{"message":"The Secret named {{request.object.spec.jobTemplate.spec.template.spec.volumes[0].secret.secretName}} is restricted and may not be used.","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"containers":[{"image":"registry.domain.com/*"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, rulePatches, expectedPatches)
|
||||
}
|
||||
|
||||
func Test_Deny(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/policy/deny/policy.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].MatchResources.Any = kyverno.ResourceFilters{
|
||||
{
|
||||
ResourceDescription: kyverno.ResourceDescription{
|
||||
Kinds: []string{"Pod"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rulePatches, errs := GenerateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
fmt.Println("utils.JoinPatches(patches)erterter", string(utils.JoinPatches(rulePatches)))
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-disallow-mount-containerd-sock","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"foreach":[{"list":"request.object.spec.template.spec.volumes[]","deny":{"conditions":{"any":[{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/var/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"\\var\\run\\containerd\\containerd.sock"}]}}}]}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-disallow-mount-containerd-sock","match":{"any":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"foreach":[{"list":"request.object.spec.jobTemplate.spec.template.spec.volumes[]","deny":{"conditions":{"any":[{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/var/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"\\var\\run\\containerd\\containerd.sock"}]}}}]}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
421
pkg/autogen/rule.go
Normal file
421
pkg/autogen/rule.go
Normal file
|
@ -0,0 +1,421 @@
|
|||
package autogen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
)
|
||||
|
||||
// the kyvernoRule holds the temporary kyverno rule struct
|
||||
// each field is a pointer to the actual object
|
||||
// when serializing data, we would expect to drop the omitempty key
|
||||
// otherwise (without the pointer), it will be set to empty value
|
||||
// - an empty struct in this case, some may fail the schema validation
|
||||
// may related to:
|
||||
// https://github.com/kyverno/kyverno/pull/549#discussion_r360088556
|
||||
// https://github.com/kyverno/kyverno/issues/568
|
||||
|
||||
type kyvernoRule struct {
|
||||
Name string `json:"name"`
|
||||
MatchResources *kyverno.MatchResources `json:"match"`
|
||||
ExcludeResources *kyverno.ExcludeResources `json:"exclude,omitempty"`
|
||||
Context *[]kyverno.ContextEntry `json:"context,omitempty"`
|
||||
AnyAllConditions *apiextensions.JSON `json:"preconditions,omitempty"`
|
||||
Mutation *kyverno.Mutation `json:"mutate,omitempty"`
|
||||
Validation *kyverno.Validation `json:"validate,omitempty"`
|
||||
VerifyImages []*kyverno.ImageVerification `json:"verifyImages,omitempty" yaml:"verifyImages,omitempty"`
|
||||
}
|
||||
|
||||
func createRuleMap(rules []kyverno.Rule) map[string]kyvernoRule {
|
||||
var ruleMap = make(map[string]kyvernoRule)
|
||||
for _, rule := range rules {
|
||||
var jsonFriendlyStruct kyvernoRule
|
||||
|
||||
jsonFriendlyStruct.Name = rule.Name
|
||||
|
||||
if !reflect.DeepEqual(rule.MatchResources, kyverno.MatchResources{}) {
|
||||
jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rule.ExcludeResources, kyverno.ExcludeResources{}) {
|
||||
jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) {
|
||||
jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
|
||||
jsonFriendlyStruct.Validation = rule.Validation.DeepCopy()
|
||||
}
|
||||
|
||||
ruleMap[rule.Name] = jsonFriendlyStruct
|
||||
}
|
||||
return ruleMap
|
||||
}
|
||||
|
||||
func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.Logger) kyvernoRule {
|
||||
logger := log.WithName("generateRuleForControllers")
|
||||
|
||||
if strings.HasPrefix(rule.Name, "autogen-") || controllers == "" {
|
||||
logger.V(5).Info("skip generateRuleForControllers")
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
logger.V(3).Info("processing rule", "rulename", rule.Name)
|
||||
|
||||
match := rule.MatchResources
|
||||
exclude := rule.ExcludeResources
|
||||
|
||||
matchResourceDescriptionsKinds := rule.MatchKinds()
|
||||
excludeResourceDescriptionsKinds := rule.ExcludeKinds()
|
||||
|
||||
if !utils.ContainsPod(matchResourceDescriptionsKinds, "Pod") ||
|
||||
(len(excludeResourceDescriptionsKinds) != 0 && !utils.ContainsPod(excludeResourceDescriptionsKinds, "Pod")) {
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
// Support backwards compatibility
|
||||
skipAutoGeneration := false
|
||||
var controllersValidated []string
|
||||
if controllers == "all" {
|
||||
skipAutoGeneration = true
|
||||
} else if controllers != "none" && controllers != "all" {
|
||||
controllersList := map[string]int{"DaemonSet": 1, "Deployment": 1, "Job": 1, "StatefulSet": 1}
|
||||
for _, value := range strings.Split(controllers, ",") {
|
||||
if _, ok := controllersList[value]; ok {
|
||||
controllersValidated = append(controllersValidated, value)
|
||||
}
|
||||
}
|
||||
if len(controllersValidated) > 0 {
|
||||
skipAutoGeneration = true
|
||||
}
|
||||
}
|
||||
|
||||
if skipAutoGeneration {
|
||||
if controllers == "all" {
|
||||
controllers = "DaemonSet,Deployment,Job,StatefulSet"
|
||||
} else {
|
||||
controllers = strings.Join(controllersValidated, ",")
|
||||
}
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("autogen-%s", rule.Name)
|
||||
if len(name) > 63 {
|
||||
name = name[:63]
|
||||
}
|
||||
|
||||
controllerRule := &kyvernoRule{
|
||||
Name: name,
|
||||
MatchResources: match.DeepCopy(),
|
||||
}
|
||||
|
||||
if len(rule.Context) > 0 {
|
||||
controllerRule.Context = &rule.DeepCopy().Context
|
||||
}
|
||||
|
||||
kyvernoAnyAllConditions, _ := utils.ApiextensionsJsonToKyvernoConditions(rule.AnyAllConditions)
|
||||
switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) {
|
||||
case kyverno.AnyAllConditions:
|
||||
if !reflect.DeepEqual(typedAnyAllConditions, kyverno.AnyAllConditions{}) {
|
||||
controllerRule.AnyAllConditions = &rule.DeepCopy().AnyAllConditions
|
||||
}
|
||||
case []kyverno.Condition:
|
||||
if len(typedAnyAllConditions) > 0 {
|
||||
controllerRule.AnyAllConditions = &rule.DeepCopy().AnyAllConditions
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(exclude, kyverno.ExcludeResources{}) {
|
||||
controllerRule.ExcludeResources = exclude.DeepCopy()
|
||||
}
|
||||
|
||||
// overwrite Kinds by pod controllers defined in the annotation
|
||||
if len(rule.MatchResources.Any) > 0 {
|
||||
rule := getAnyAllAutogenRule(controllerRule.MatchResources.Any, controllers)
|
||||
controllerRule.MatchResources.Any = rule
|
||||
} else if len(rule.MatchResources.All) > 0 {
|
||||
rule := getAnyAllAutogenRule(controllerRule.MatchResources.All, controllers)
|
||||
controllerRule.MatchResources.All = rule
|
||||
} else {
|
||||
controllerRule.MatchResources.Kinds = strings.Split(controllers, ",")
|
||||
}
|
||||
|
||||
if len(rule.ExcludeResources.Any) > 0 {
|
||||
rule := getAnyAllAutogenRule(controllerRule.ExcludeResources.Any, controllers)
|
||||
controllerRule.ExcludeResources.Any = rule
|
||||
} else if len(rule.ExcludeResources.All) > 0 {
|
||||
rule := getAnyAllAutogenRule(controllerRule.ExcludeResources.All, controllers)
|
||||
controllerRule.ExcludeResources.All = rule
|
||||
} else {
|
||||
if len(exclude.Kinds) != 0 {
|
||||
controllerRule.ExcludeResources.Kinds = strings.Split(controllers, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Mutation.PatchStrategicMerge != nil {
|
||||
newMutation := &kyverno.Mutation{
|
||||
PatchStrategicMerge: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": rule.Mutation.PatchStrategicMerge,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
controllerRule.Mutation = newMutation.DeepCopy()
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if len(rule.Mutation.ForEachMutation) > 0 && rule.Mutation.ForEachMutation != nil {
|
||||
var newForeachMutation []*kyverno.ForEachMutation
|
||||
for _, foreach := range rule.Mutation.ForEachMutation {
|
||||
newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{
|
||||
List: foreach.List,
|
||||
Context: foreach.Context,
|
||||
AnyAllConditions: foreach.AnyAllConditions,
|
||||
PatchStrategicMerge: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": foreach.PatchStrategicMerge,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
controllerRule.Mutation = &kyverno.Mutation{
|
||||
ForEachMutation: newForeachMutation,
|
||||
}
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.Validation.Pattern != nil {
|
||||
newValidate := &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
|
||||
Pattern: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": rule.Validation.Pattern,
|
||||
},
|
||||
},
|
||||
}
|
||||
controllerRule.Validation = newValidate.DeepCopy()
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.Validation.Deny != nil {
|
||||
deny := &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "deny"),
|
||||
Deny: rule.Validation.Deny,
|
||||
}
|
||||
controllerRule.Validation = deny.DeepCopy()
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.Validation.AnyPattern != nil {
|
||||
|
||||
anyPatterns, err := rule.Validation.DeserializeAnyPattern()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to deserialize anyPattern, expect type array")
|
||||
}
|
||||
|
||||
patterns := validateAnyPattern(anyPatterns)
|
||||
controllerRule.Validation = &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "anyPattern"),
|
||||
AnyPattern: patterns,
|
||||
}
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if len(rule.Validation.ForEachValidation) > 0 && rule.Validation.ForEachValidation != nil {
|
||||
newForeachValidate := make([]*kyverno.ForEachValidation, len(rule.Validation.ForEachValidation))
|
||||
for i, foreach := range rule.Validation.ForEachValidation {
|
||||
newForeachValidate[i] = foreach
|
||||
}
|
||||
controllerRule.Validation = &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
|
||||
ForEachValidation: newForeachValidate,
|
||||
}
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.VerifyImages != nil {
|
||||
newVerifyImages := make([]*kyverno.ImageVerification, len(rule.VerifyImages))
|
||||
for i, vi := range rule.VerifyImages {
|
||||
newVerifyImages[i] = vi.DeepCopy()
|
||||
}
|
||||
|
||||
controllerRule.VerifyImages = newVerifyImages
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
func generateCronJobRule(rule kyverno.Rule, controllers string, log logr.Logger) kyvernoRule {
|
||||
logger := log.WithName("handleCronJob")
|
||||
|
||||
hasCronJob := strings.Contains(controllers, engine.PodControllerCronJob) || strings.Contains(controllers, "all")
|
||||
if !hasCronJob {
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
logger.V(3).Info("generating rule for cronJob")
|
||||
jobRule := generateRuleForControllers(rule, "Job", logger)
|
||||
|
||||
if reflect.DeepEqual(jobRule, kyvernoRule{}) {
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
cronJobRule := &jobRule
|
||||
|
||||
name := fmt.Sprintf("autogen-cronjob-%s", rule.Name)
|
||||
if len(name) > 63 {
|
||||
name = name[:63]
|
||||
}
|
||||
cronJobRule.Name = name
|
||||
|
||||
if len(jobRule.MatchResources.Any) > 0 {
|
||||
rule := cronJobAnyAllAutogenRule(cronJobRule.MatchResources.Any)
|
||||
cronJobRule.MatchResources.Any = rule
|
||||
} else if len(jobRule.MatchResources.All) > 0 {
|
||||
rule := cronJobAnyAllAutogenRule(cronJobRule.MatchResources.All)
|
||||
cronJobRule.MatchResources.All = rule
|
||||
} else {
|
||||
cronJobRule.MatchResources.Kinds = []string{engine.PodControllerCronJob}
|
||||
}
|
||||
|
||||
if (jobRule.ExcludeResources) != nil && len(jobRule.ExcludeResources.Any) > 0 {
|
||||
rule := cronJobAnyAllAutogenRule(cronJobRule.ExcludeResources.Any)
|
||||
cronJobRule.ExcludeResources.Any = rule
|
||||
} else if (jobRule.ExcludeResources) != nil && len(jobRule.ExcludeResources.All) > 0 {
|
||||
rule := cronJobAnyAllAutogenRule(cronJobRule.ExcludeResources.All)
|
||||
cronJobRule.ExcludeResources.All = rule
|
||||
} else {
|
||||
if (jobRule.ExcludeResources) != nil && (len(jobRule.ExcludeResources.Kinds) > 0) {
|
||||
cronJobRule.ExcludeResources.Kinds = []string{engine.PodControllerCronJob}
|
||||
}
|
||||
}
|
||||
|
||||
if (jobRule.Mutation != nil) && (jobRule.Mutation.PatchStrategicMerge != nil) {
|
||||
newMutation := &kyverno.Mutation{
|
||||
PatchStrategicMerge: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"jobTemplate": jobRule.Mutation.PatchStrategicMerge,
|
||||
},
|
||||
},
|
||||
}
|
||||
cronJobRule.Mutation = newMutation.DeepCopy()
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Validation != nil) && (jobRule.Validation.Pattern != nil) {
|
||||
newValidate := &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"),
|
||||
Pattern: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"jobTemplate": jobRule.Validation.Pattern,
|
||||
},
|
||||
},
|
||||
}
|
||||
cronJobRule.Validation = newValidate.DeepCopy()
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Validation != nil) && (jobRule.Validation.Deny != nil) {
|
||||
newValidate := &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"),
|
||||
Deny: jobRule.Validation.Deny,
|
||||
}
|
||||
cronJobRule.Validation = newValidate.DeepCopy()
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Validation != nil) && (jobRule.Validation.AnyPattern != nil) {
|
||||
var patterns []interface{}
|
||||
anyPatterns, err := jobRule.Validation.DeserializeAnyPattern()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to deserialize anyPattern, expect type array")
|
||||
}
|
||||
|
||||
for _, pattern := range anyPatterns {
|
||||
newPattern := map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"jobTemplate": pattern,
|
||||
},
|
||||
}
|
||||
|
||||
patterns = append(patterns, newPattern)
|
||||
}
|
||||
|
||||
cronJobRule.Validation = &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "anyPattern"),
|
||||
AnyPattern: patterns,
|
||||
}
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Validation != nil) && len(jobRule.Validation.ForEachValidation) > 0 && jobRule.Validation.ForEachValidation != nil {
|
||||
newForeachValidate := make([]*kyverno.ForEachValidation, len(jobRule.Validation.ForEachValidation))
|
||||
for i, foreach := range rule.Validation.ForEachValidation {
|
||||
newForeachValidate[i] = foreach
|
||||
}
|
||||
cronJobRule.Validation = &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
|
||||
ForEachValidation: newForeachValidate,
|
||||
}
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Mutation != nil) && len(jobRule.Mutation.ForEachMutation) > 0 && jobRule.Mutation.ForEachMutation != nil {
|
||||
|
||||
var newForeachMutation []*kyverno.ForEachMutation
|
||||
|
||||
for _, foreach := range jobRule.Mutation.ForEachMutation {
|
||||
newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{
|
||||
List: foreach.List,
|
||||
Context: foreach.Context,
|
||||
AnyAllConditions: foreach.AnyAllConditions,
|
||||
PatchStrategicMerge: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"jobTemplate": foreach.PatchStrategicMerge,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
cronJobRule.Mutation = &kyverno.Mutation{
|
||||
ForEachMutation: newForeachMutation,
|
||||
}
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if jobRule.VerifyImages != nil {
|
||||
newVerifyImages := make([]*kyverno.ImageVerification, len(jobRule.VerifyImages))
|
||||
for i, vi := range rule.VerifyImages {
|
||||
newVerifyImages[i] = vi.DeepCopy()
|
||||
}
|
||||
cronJobRule.VerifyImages = newVerifyImages
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
func updateGenRuleByte(pbyte []byte, kind string, genRule kyvernoRule) (obj []byte) {
|
||||
// TODO: do we need to unmarshall here ?
|
||||
if err := json.Unmarshal(pbyte, &genRule); err != nil {
|
||||
return obj
|
||||
}
|
||||
if kind == "Pod" {
|
||||
obj = []byte(strings.Replace(string(pbyte), "request.object.spec", "request.object.spec.template.spec", -1))
|
||||
}
|
||||
if kind == "Cronjob" {
|
||||
obj = []byte(strings.Replace(string(pbyte), "request.object.spec", "request.object.spec.jobTemplate.spec.template.spec", -1))
|
||||
}
|
||||
obj = []byte(strings.Replace(string(obj), "request.object.metadata", "request.object.spec.template.metadata", -1))
|
||||
return obj
|
||||
}
|
74
pkg/autogen/utils.go
Normal file
74
pkg/autogen/utils.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package autogen
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
)
|
||||
|
||||
func isKindOtherthanPod(kinds []string) bool {
|
||||
if len(kinds) > 1 && utils.ContainsPod(kinds, "Pod") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasAutogenKinds(kind []string) bool {
|
||||
for _, v := range kind {
|
||||
if v == "Pod" || strings.Contains(engine.PodControllers, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validateAnyPattern(anyPatterns []interface{}) []interface{} {
|
||||
var patterns []interface{}
|
||||
for _, pattern := range anyPatterns {
|
||||
newPattern := map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": pattern,
|
||||
},
|
||||
}
|
||||
patterns = append(patterns, newPattern)
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
|
||||
func getAnyAllAutogenRule(v kyverno.ResourceFilters, controllers string) kyverno.ResourceFilters {
|
||||
anyKind := v.DeepCopy()
|
||||
for i, value := range v {
|
||||
if utils.ContainsPod(value.Kinds, "Pod") {
|
||||
anyKind[i].Kinds = strings.Split(controllers, ",")
|
||||
}
|
||||
}
|
||||
return anyKind
|
||||
}
|
||||
|
||||
// stripCronJob removes CronJob from controllers
|
||||
func stripCronJob(controllers string) string {
|
||||
var newControllers []string
|
||||
controllerArr := strings.Split(controllers, ",")
|
||||
for _, c := range controllerArr {
|
||||
if c == engine.PodControllerCronJob {
|
||||
continue
|
||||
}
|
||||
newControllers = append(newControllers, c)
|
||||
}
|
||||
if len(newControllers) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(newControllers, ",")
|
||||
}
|
||||
|
||||
func cronJobAnyAllAutogenRule(v kyverno.ResourceFilters) kyverno.ResourceFilters {
|
||||
anyKind := v.DeepCopy()
|
||||
for i, value := range v {
|
||||
if utils.ContainsPod(value.Kinds, "Job") {
|
||||
anyKind[i].Kinds = []string{engine.PodControllerCronJob}
|
||||
}
|
||||
}
|
||||
return anyKind
|
||||
}
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/autogen"
|
||||
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned/scheme"
|
||||
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
|
||||
|
@ -21,7 +22,6 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/event"
|
||||
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||
"github.com/kyverno/kyverno/pkg/metrics"
|
||||
pm "github.com/kyverno/kyverno/pkg/policymutation"
|
||||
"github.com/kyverno/kyverno/pkg/policyreport"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
@ -563,7 +563,7 @@ func updateGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList [
|
|||
func missingAutoGenRules(policy *kyverno.ClusterPolicy, log logr.Logger) bool {
|
||||
var podRuleName []string
|
||||
ruleCount := 1
|
||||
if canApplyAutoGen, _ := pm.CanAutoGen(&policy.Spec, log); canApplyAutoGen {
|
||||
if canApplyAutoGen, _ := autogen.CanAutoGen(&policy.Spec, log); canApplyAutoGen {
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
podRuleName = append(podRuleName, rule.Name)
|
||||
}
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
package policymutation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
)
|
||||
|
||||
func generateCronJobRule(rule kyverno.Rule, controllers string, log logr.Logger) kyvernoRule {
|
||||
logger := log.WithName("handleCronJob")
|
||||
|
||||
hasCronJob := strings.Contains(controllers, engine.PodControllerCronJob) || strings.Contains(controllers, "all")
|
||||
if !hasCronJob {
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
logger.V(3).Info("generating rule for cronJob")
|
||||
jobRule := generateRuleForControllers(rule, "Job", logger)
|
||||
|
||||
if reflect.DeepEqual(jobRule, kyvernoRule{}) {
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
cronJobRule := &jobRule
|
||||
|
||||
name := fmt.Sprintf("autogen-cronjob-%s", rule.Name)
|
||||
if len(name) > 63 {
|
||||
name = name[:63]
|
||||
}
|
||||
cronJobRule.Name = name
|
||||
|
||||
if len(jobRule.MatchResources.Any) > 0 {
|
||||
rule := cronJobAnyAllAutogenRule(cronJobRule.MatchResources.Any)
|
||||
cronJobRule.MatchResources.Any = rule
|
||||
} else if len(jobRule.MatchResources.All) > 0 {
|
||||
rule := cronJobAnyAllAutogenRule(cronJobRule.MatchResources.All)
|
||||
cronJobRule.MatchResources.All = rule
|
||||
} else {
|
||||
cronJobRule.MatchResources.Kinds = []string{engine.PodControllerCronJob}
|
||||
}
|
||||
|
||||
if (jobRule.ExcludeResources) != nil && len(jobRule.ExcludeResources.Any) > 0 {
|
||||
rule := cronJobAnyAllAutogenRule(cronJobRule.ExcludeResources.Any)
|
||||
cronJobRule.ExcludeResources.Any = rule
|
||||
} else if (jobRule.ExcludeResources) != nil && len(jobRule.ExcludeResources.All) > 0 {
|
||||
rule := cronJobAnyAllAutogenRule(cronJobRule.ExcludeResources.All)
|
||||
cronJobRule.ExcludeResources.All = rule
|
||||
} else {
|
||||
if (jobRule.ExcludeResources) != nil && (len(jobRule.ExcludeResources.Kinds) > 0) {
|
||||
cronJobRule.ExcludeResources.Kinds = []string{engine.PodControllerCronJob}
|
||||
}
|
||||
}
|
||||
|
||||
if (jobRule.Mutation != nil) && (jobRule.Mutation.PatchStrategicMerge != nil) {
|
||||
newMutation := &kyverno.Mutation{
|
||||
PatchStrategicMerge: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"jobTemplate": jobRule.Mutation.PatchStrategicMerge,
|
||||
},
|
||||
},
|
||||
}
|
||||
cronJobRule.Mutation = newMutation.DeepCopy()
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Validation != nil) && (jobRule.Validation.Pattern != nil) {
|
||||
newValidate := &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"),
|
||||
Pattern: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"jobTemplate": jobRule.Validation.Pattern,
|
||||
},
|
||||
},
|
||||
}
|
||||
cronJobRule.Validation = newValidate.DeepCopy()
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Validation != nil) && (jobRule.Validation.Deny != nil) {
|
||||
newValidate := &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"),
|
||||
Deny: jobRule.Validation.Deny,
|
||||
}
|
||||
cronJobRule.Validation = newValidate.DeepCopy()
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Validation != nil) && (jobRule.Validation.AnyPattern != nil) {
|
||||
var patterns []interface{}
|
||||
anyPatterns, err := jobRule.Validation.DeserializeAnyPattern()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to deserialize anyPattern, expect type array")
|
||||
}
|
||||
|
||||
for _, pattern := range anyPatterns {
|
||||
newPattern := map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"jobTemplate": pattern,
|
||||
},
|
||||
}
|
||||
|
||||
patterns = append(patterns, newPattern)
|
||||
}
|
||||
|
||||
cronJobRule.Validation = &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "anyPattern"),
|
||||
AnyPattern: patterns,
|
||||
}
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Validation != nil) && len(jobRule.Validation.ForEachValidation) > 0 && jobRule.Validation.ForEachValidation != nil {
|
||||
newForeachValidate := make([]*kyverno.ForEachValidation, len(jobRule.Validation.ForEachValidation))
|
||||
for i, foreach := range rule.Validation.ForEachValidation {
|
||||
newForeachValidate[i] = foreach
|
||||
}
|
||||
cronJobRule.Validation = &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
|
||||
ForEachValidation: newForeachValidate,
|
||||
}
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if (jobRule.Mutation != nil) && len(jobRule.Mutation.ForEachMutation) > 0 && jobRule.Mutation.ForEachMutation != nil {
|
||||
|
||||
var newForeachMutation []*kyverno.ForEachMutation
|
||||
|
||||
for _, foreach := range jobRule.Mutation.ForEachMutation {
|
||||
newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{
|
||||
List: foreach.List,
|
||||
Context: foreach.Context,
|
||||
AnyAllConditions: foreach.AnyAllConditions,
|
||||
PatchStrategicMerge: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"jobTemplate": foreach.PatchStrategicMerge,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
cronJobRule.Mutation = &kyverno.Mutation{
|
||||
ForEachMutation: newForeachMutation,
|
||||
}
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
if jobRule.VerifyImages != nil {
|
||||
newVerifyImages := make([]*kyverno.ImageVerification, len(jobRule.VerifyImages))
|
||||
for i, vi := range rule.VerifyImages {
|
||||
newVerifyImages[i] = vi.DeepCopy()
|
||||
}
|
||||
cronJobRule.VerifyImages = newVerifyImages
|
||||
return *cronJobRule
|
||||
}
|
||||
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
// stripCronJob removes CronJob from controllers
|
||||
func stripCronJob(controllers string) string {
|
||||
var newControllers []string
|
||||
|
||||
controllerArr := strings.Split(controllers, ",")
|
||||
for _, c := range controllerArr {
|
||||
if c == engine.PodControllerCronJob {
|
||||
continue
|
||||
}
|
||||
|
||||
newControllers = append(newControllers, c)
|
||||
}
|
||||
|
||||
if len(newControllers) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.Join(newControllers, ",")
|
||||
}
|
||||
|
||||
func cronJobAnyAllAutogenRule(v kyverno.ResourceFilters) kyverno.ResourceFilters {
|
||||
anyKind := v.DeepCopy()
|
||||
for i, value := range v {
|
||||
if utils.ContainsPod(value.Kinds, "Job") {
|
||||
anyKind[i].Kinds = []string{engine.PodControllerCronJob}
|
||||
}
|
||||
}
|
||||
return anyKind
|
||||
}
|
|
@ -3,18 +3,15 @@ package policymutation
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/autogen"
|
||||
"github.com/kyverno/kyverno/pkg/common"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
)
|
||||
|
||||
// GenerateJSONPatchesForDefaults generates default JSON patches for
|
||||
|
@ -253,7 +250,7 @@ func defaultFailurePolicy(spec *kyverno.Spec, log logr.Logger) ([]byte, string)
|
|||
|
||||
// GeneratePodControllerRule returns two patches: rulePatches and annotation patch(if necessary)
|
||||
func GeneratePodControllerRule(policy kyverno.ClusterPolicy, log logr.Logger) (patches [][]byte, errs []error) {
|
||||
applyAutoGen, desiredControllers := CanAutoGen(&policy.Spec, log)
|
||||
applyAutoGen, desiredControllers := autogen.CanAutoGen(&policy.Spec, log)
|
||||
|
||||
if !applyAutoGen {
|
||||
desiredControllers = "none"
|
||||
|
@ -285,473 +282,12 @@ func GeneratePodControllerRule(policy kyverno.ClusterPolicy, log logr.Logger) (p
|
|||
|
||||
log.V(3).Info("auto generating rule for pod controllers", "controllers", actualControllers)
|
||||
|
||||
p, err := generateRulePatches(&policy.Spec, actualControllers, log)
|
||||
p, err := autogen.GenerateRulePatches(&policy.Spec, actualControllers, log)
|
||||
patches = append(patches, p...)
|
||||
errs = append(errs, err...)
|
||||
return
|
||||
}
|
||||
|
||||
// CanAutoGen checks whether the rule(s) (in policy) can be applied to Pod controllers
|
||||
// returns controllers as:
|
||||
// - "" if:
|
||||
// - name or selector is defined
|
||||
// - mixed kinds (Pod + pod controller) is defined
|
||||
// - Pod and PodControllers are not defined
|
||||
// - mutate.Patches/mutate.PatchesJSON6902/validate.deny/generate rule is defined
|
||||
// - otherwise it returns all pod controllers
|
||||
func CanAutoGen(spec *kyverno.Spec, log logr.Logger) (applyAutoGen bool, controllers string) {
|
||||
var needAutogen bool
|
||||
for _, rule := range spec.Rules {
|
||||
match := rule.MatchResources
|
||||
exclude := rule.ExcludeResources
|
||||
|
||||
if match.ResourceDescription.Name != "" || match.ResourceDescription.Selector != nil || match.ResourceDescription.Annotations != nil ||
|
||||
exclude.ResourceDescription.Name != "" || exclude.ResourceDescription.Selector != nil || exclude.ResourceDescription.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in resource description may not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if isKindOtherthanPod(match.Kinds) || isKindOtherthanPod(exclude.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
needAutogen = hasAutogenKinds(match.Kinds) || hasAutogenKinds(exclude.Kinds)
|
||||
|
||||
for _, value := range match.Any {
|
||||
if isKindOtherthanPod(value.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
if !needAutogen {
|
||||
needAutogen = hasAutogenKinds(value.Kinds)
|
||||
}
|
||||
if value.Name != "" || value.Selector != nil || value.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in match any block is not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
for _, value := range match.All {
|
||||
if isKindOtherthanPod(value.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
if !needAutogen {
|
||||
needAutogen = hasAutogenKinds(value.Kinds)
|
||||
}
|
||||
if value.Name != "" || value.Selector != nil || value.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in match all block is not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
for _, value := range exclude.Any {
|
||||
if isKindOtherthanPod(value.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
if !needAutogen {
|
||||
needAutogen = hasAutogenKinds(value.Kinds)
|
||||
}
|
||||
if value.Name != "" || value.Selector != nil || value.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in exclude any block is not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
for _, value := range exclude.All {
|
||||
if isKindOtherthanPod(value.Kinds) {
|
||||
return false, ""
|
||||
}
|
||||
if !needAutogen {
|
||||
needAutogen = hasAutogenKinds(value.Kinds)
|
||||
}
|
||||
if value.Name != "" || value.Selector != nil || value.Annotations != nil {
|
||||
log.V(3).Info("skip generating rule on pod controllers: Name / Selector in exclud all block is not be applicable.", "rule", rule.Name)
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Mutation.PatchesJSON6902 != "" || rule.HasGenerate() {
|
||||
return false, "none"
|
||||
}
|
||||
}
|
||||
|
||||
if !needAutogen {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, engine.PodControllers
|
||||
}
|
||||
|
||||
func isKindOtherthanPod(kinds []string) bool {
|
||||
if len(kinds) > 1 && utils.ContainsPod(kinds, "Pod") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasAutogenKinds(kind []string) bool {
|
||||
for _, v := range kind {
|
||||
if v == "Pod" || strings.Contains(engine.PodControllers, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func createRuleMap(rules []kyverno.Rule) map[string]kyvernoRule {
|
||||
var ruleMap = make(map[string]kyvernoRule)
|
||||
for _, rule := range rules {
|
||||
var jsonFriendlyStruct kyvernoRule
|
||||
|
||||
jsonFriendlyStruct.Name = rule.Name
|
||||
|
||||
if !reflect.DeepEqual(rule.MatchResources, kyverno.MatchResources{}) {
|
||||
jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rule.ExcludeResources, kyverno.ExcludeResources{}) {
|
||||
jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) {
|
||||
jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
|
||||
jsonFriendlyStruct.Validation = rule.Validation.DeepCopy()
|
||||
}
|
||||
|
||||
ruleMap[rule.Name] = jsonFriendlyStruct
|
||||
}
|
||||
return ruleMap
|
||||
}
|
||||
func updateGenRuleByte(pbyte []byte, kind string, genRule kyvernoRule) (obj []byte) {
|
||||
if err := json.Unmarshal(pbyte, &genRule); err != nil {
|
||||
return obj
|
||||
}
|
||||
if kind == "Pod" {
|
||||
obj = []byte(strings.Replace(string(pbyte), "request.object.spec", "request.object.spec.template.spec", -1))
|
||||
}
|
||||
if kind == "Cronjob" {
|
||||
obj = []byte(strings.Replace(string(pbyte), "request.object.spec", "request.object.spec.jobTemplate.spec.template.spec", -1))
|
||||
}
|
||||
obj = []byte(strings.Replace(string(obj), "request.object.metadata", "request.object.spec.template.metadata", -1))
|
||||
return obj
|
||||
}
|
||||
|
||||
// generateRulePatches generates rule for podControllers based on scenario A and C
|
||||
func generateRulePatches(spec *kyverno.Spec, controllers string, log logr.Logger) (rulePatches [][]byte, errs []error) {
|
||||
insertIdx := len(spec.Rules)
|
||||
|
||||
ruleMap := createRuleMap(spec.Rules)
|
||||
var ruleIndex = make(map[string]int)
|
||||
for index, rule := range spec.Rules {
|
||||
ruleIndex[rule.Name] = index
|
||||
}
|
||||
|
||||
for _, rule := range spec.Rules {
|
||||
patchPostion := insertIdx
|
||||
convertToPatches := func(genRule kyvernoRule, patchPostion int) []byte {
|
||||
operation := "add"
|
||||
if existingAutoGenRule, alreadyExists := ruleMap[genRule.Name]; alreadyExists {
|
||||
existingAutoGenRuleRaw, _ := json.Marshal(existingAutoGenRule)
|
||||
genRuleRaw, _ := json.Marshal(genRule)
|
||||
|
||||
if string(existingAutoGenRuleRaw) == string(genRuleRaw) {
|
||||
return nil
|
||||
}
|
||||
operation = "replace"
|
||||
patchPostion = ruleIndex[genRule.Name]
|
||||
}
|
||||
|
||||
// generate patch bytes
|
||||
jsonPatch := struct {
|
||||
Path string `json:"path"`
|
||||
Op string `json:"op"`
|
||||
Value interface{} `json:"value"`
|
||||
}{
|
||||
fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)),
|
||||
operation,
|
||||
genRule,
|
||||
}
|
||||
pbytes, err := json.Marshal(jsonPatch)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// check the patch
|
||||
if _, err := jsonpatch.DecodePatch([]byte("[" + string(pbytes) + "]")); err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return pbytes
|
||||
}
|
||||
|
||||
// handle all other controllers other than CronJob
|
||||
genRule := generateRuleForControllers(rule, stripCronJob(controllers), log)
|
||||
if !reflect.DeepEqual(genRule, kyvernoRule{}) {
|
||||
pbytes := convertToPatches(genRule, patchPostion)
|
||||
pbytes = updateGenRuleByte(pbytes, "Pod", genRule)
|
||||
if pbytes != nil {
|
||||
rulePatches = append(rulePatches, pbytes)
|
||||
}
|
||||
insertIdx++
|
||||
patchPostion = insertIdx
|
||||
}
|
||||
|
||||
// handle CronJob, it appends an additional rule
|
||||
genRule = generateCronJobRule(rule, controllers, log)
|
||||
|
||||
if !reflect.DeepEqual(genRule, kyvernoRule{}) {
|
||||
pbytes := convertToPatches(genRule, patchPostion)
|
||||
pbytes = updateGenRuleByte(pbytes, "Cronjob", genRule)
|
||||
if pbytes != nil {
|
||||
rulePatches = append(rulePatches, pbytes)
|
||||
}
|
||||
insertIdx++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// the kyvernoRule holds the temporary kyverno rule struct
|
||||
// each field is a pointer to the actual object
|
||||
// when serializing data, we would expect to drop the omitempty key
|
||||
// otherwise (without the pointer), it will be set to empty value
|
||||
// - an empty struct in this case, some may fail the schema validation
|
||||
// may related to:
|
||||
// https://github.com/kyverno/kyverno/pull/549#discussion_r360088556
|
||||
// https://github.com/kyverno/kyverno/issues/568
|
||||
|
||||
type kyvernoRule struct {
|
||||
Name string `json:"name"`
|
||||
MatchResources *kyverno.MatchResources `json:"match"`
|
||||
ExcludeResources *kyverno.ExcludeResources `json:"exclude,omitempty"`
|
||||
Context *[]kyverno.ContextEntry `json:"context,omitempty"`
|
||||
AnyAllConditions *apiextensions.JSON `json:"preconditions,omitempty"`
|
||||
Mutation *kyverno.Mutation `json:"mutate,omitempty"`
|
||||
Validation *kyverno.Validation `json:"validate,omitempty"`
|
||||
VerifyImages []*kyverno.ImageVerification `json:"verifyImages,omitempty" yaml:"verifyImages,omitempty"`
|
||||
}
|
||||
|
||||
func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.Logger) kyvernoRule {
|
||||
logger := log.WithName("generateRuleForControllers")
|
||||
|
||||
if strings.HasPrefix(rule.Name, "autogen-") || controllers == "" {
|
||||
logger.V(5).Info("skip generateRuleForControllers")
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
logger.V(3).Info("processing rule", "rulename", rule.Name)
|
||||
|
||||
match := rule.MatchResources
|
||||
exclude := rule.ExcludeResources
|
||||
|
||||
matchResourceDescriptionsKinds := rule.MatchKinds()
|
||||
excludeResourceDescriptionsKinds := rule.ExcludeKinds()
|
||||
|
||||
if !utils.ContainsPod(matchResourceDescriptionsKinds, "Pod") ||
|
||||
(len(excludeResourceDescriptionsKinds) != 0 && !utils.ContainsPod(excludeResourceDescriptionsKinds, "Pod")) {
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
// Support backwards compatibility
|
||||
skipAutoGeneration := false
|
||||
var controllersValidated []string
|
||||
if controllers == "all" {
|
||||
skipAutoGeneration = true
|
||||
} else if controllers != "none" && controllers != "all" {
|
||||
controllersList := map[string]int{"DaemonSet": 1, "Deployment": 1, "Job": 1, "StatefulSet": 1}
|
||||
for _, value := range strings.Split(controllers, ",") {
|
||||
if _, ok := controllersList[value]; ok {
|
||||
controllersValidated = append(controllersValidated, value)
|
||||
}
|
||||
}
|
||||
if len(controllersValidated) > 0 {
|
||||
skipAutoGeneration = true
|
||||
}
|
||||
}
|
||||
|
||||
if skipAutoGeneration {
|
||||
if controllers == "all" {
|
||||
controllers = "DaemonSet,Deployment,Job,StatefulSet"
|
||||
} else {
|
||||
controllers = strings.Join(controllersValidated, ",")
|
||||
}
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("autogen-%s", rule.Name)
|
||||
if len(name) > 63 {
|
||||
name = name[:63]
|
||||
}
|
||||
|
||||
controllerRule := &kyvernoRule{
|
||||
Name: name,
|
||||
MatchResources: match.DeepCopy(),
|
||||
}
|
||||
|
||||
if len(rule.Context) > 0 {
|
||||
controllerRule.Context = &rule.DeepCopy().Context
|
||||
}
|
||||
|
||||
kyvernoAnyAllConditions, _ := utils.ApiextensionsJsonToKyvernoConditions(rule.AnyAllConditions)
|
||||
switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) {
|
||||
case kyverno.AnyAllConditions:
|
||||
if !reflect.DeepEqual(typedAnyAllConditions, kyverno.AnyAllConditions{}) {
|
||||
controllerRule.AnyAllConditions = &rule.DeepCopy().AnyAllConditions
|
||||
}
|
||||
case []kyverno.Condition:
|
||||
if len(typedAnyAllConditions) > 0 {
|
||||
controllerRule.AnyAllConditions = &rule.DeepCopy().AnyAllConditions
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(exclude, kyverno.ExcludeResources{}) {
|
||||
controllerRule.ExcludeResources = exclude.DeepCopy()
|
||||
}
|
||||
|
||||
// overwrite Kinds by pod controllers defined in the annotation
|
||||
if len(rule.MatchResources.Any) > 0 {
|
||||
rule := getAnyAllAutogenRule(controllerRule.MatchResources.Any, controllers)
|
||||
controllerRule.MatchResources.Any = rule
|
||||
} else if len(rule.MatchResources.All) > 0 {
|
||||
rule := getAnyAllAutogenRule(controllerRule.MatchResources.All, controllers)
|
||||
controllerRule.MatchResources.All = rule
|
||||
} else {
|
||||
controllerRule.MatchResources.Kinds = strings.Split(controllers, ",")
|
||||
}
|
||||
|
||||
if len(rule.ExcludeResources.Any) > 0 {
|
||||
rule := getAnyAllAutogenRule(controllerRule.ExcludeResources.Any, controllers)
|
||||
controllerRule.ExcludeResources.Any = rule
|
||||
} else if len(rule.ExcludeResources.All) > 0 {
|
||||
rule := getAnyAllAutogenRule(controllerRule.ExcludeResources.All, controllers)
|
||||
controllerRule.ExcludeResources.All = rule
|
||||
} else {
|
||||
if len(exclude.Kinds) != 0 {
|
||||
controllerRule.ExcludeResources.Kinds = strings.Split(controllers, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Mutation.PatchStrategicMerge != nil {
|
||||
newMutation := &kyverno.Mutation{
|
||||
PatchStrategicMerge: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": rule.Mutation.PatchStrategicMerge,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
controllerRule.Mutation = newMutation.DeepCopy()
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if len(rule.Mutation.ForEachMutation) > 0 && rule.Mutation.ForEachMutation != nil {
|
||||
var newForeachMutation []*kyverno.ForEachMutation
|
||||
for _, foreach := range rule.Mutation.ForEachMutation {
|
||||
newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{
|
||||
List: foreach.List,
|
||||
Context: foreach.Context,
|
||||
AnyAllConditions: foreach.AnyAllConditions,
|
||||
PatchStrategicMerge: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": foreach.PatchStrategicMerge,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
controllerRule.Mutation = &kyverno.Mutation{
|
||||
ForEachMutation: newForeachMutation,
|
||||
}
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.Validation.Pattern != nil {
|
||||
newValidate := &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
|
||||
Pattern: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": rule.Validation.Pattern,
|
||||
},
|
||||
},
|
||||
}
|
||||
controllerRule.Validation = newValidate.DeepCopy()
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.Validation.Deny != nil {
|
||||
deny := &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "deny"),
|
||||
Deny: rule.Validation.Deny,
|
||||
}
|
||||
controllerRule.Validation = deny.DeepCopy()
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.Validation.AnyPattern != nil {
|
||||
|
||||
anyPatterns, err := rule.Validation.DeserializeAnyPattern()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to deserialize anyPattern, expect type array")
|
||||
}
|
||||
|
||||
patterns := validateAnyPattern(anyPatterns)
|
||||
controllerRule.Validation = &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "anyPattern"),
|
||||
AnyPattern: patterns,
|
||||
}
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if len(rule.Validation.ForEachValidation) > 0 && rule.Validation.ForEachValidation != nil {
|
||||
newForeachValidate := make([]*kyverno.ForEachValidation, len(rule.Validation.ForEachValidation))
|
||||
for i, foreach := range rule.Validation.ForEachValidation {
|
||||
newForeachValidate[i] = foreach
|
||||
}
|
||||
controllerRule.Validation = &kyverno.Validation{
|
||||
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
|
||||
ForEachValidation: newForeachValidate,
|
||||
}
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
if rule.VerifyImages != nil {
|
||||
newVerifyImages := make([]*kyverno.ImageVerification, len(rule.VerifyImages))
|
||||
for i, vi := range rule.VerifyImages {
|
||||
newVerifyImages[i] = vi.DeepCopy()
|
||||
}
|
||||
|
||||
controllerRule.VerifyImages = newVerifyImages
|
||||
return *controllerRule
|
||||
}
|
||||
|
||||
return kyvernoRule{}
|
||||
}
|
||||
|
||||
func validateAnyPattern(anyPatterns []interface{}) []interface{} {
|
||||
var patterns []interface{}
|
||||
for _, pattern := range anyPatterns {
|
||||
newPattern := map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": pattern,
|
||||
},
|
||||
}
|
||||
|
||||
patterns = append(patterns, newPattern)
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
|
||||
func getAnyAllAutogenRule(v kyverno.ResourceFilters, controllers string) kyverno.ResourceFilters {
|
||||
anyKind := v.DeepCopy()
|
||||
|
||||
for i, value := range v {
|
||||
if utils.ContainsPod(value.Kinds, "Pod") {
|
||||
anyKind[i].Kinds = strings.Split(controllers, ",")
|
||||
}
|
||||
}
|
||||
return anyKind
|
||||
}
|
||||
|
||||
// defaultPodControllerAnnotation inserts an annotation
|
||||
// "pod-policies.kyverno.io/autogen-controllers=<controllers>" to policy
|
||||
func defaultPodControllerAnnotation(ann map[string]string, controllers string) ([]byte, error) {
|
||||
|
|
|
@ -3,15 +3,11 @@ package policymutation
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"gotest.tools/assert"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
@ -25,367 +21,6 @@ func currentDir() (string, error) {
|
|||
return filepath.Join(homedir, "github.com/kyverno/kyverno"), nil
|
||||
}
|
||||
|
||||
func Test_Any(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].MatchResources.Any = kyverno.ResourceFilters{
|
||||
{
|
||||
ResourceDescription: kyverno.ResourceDescription{
|
||||
Kinds: []string{"Pod"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
fmt.Println("utils.JoinPatches(patches)erterter", string(utils.JoinPatches(rulePatches)))
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"any":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_All(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].MatchResources.All = kyverno.ResourceFilters{
|
||||
{
|
||||
ResourceDescription: kyverno.ResourceDescription{
|
||||
Kinds: []string{"Pod"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"all":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"all":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Exclude(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].ExcludeResources.Namespaces = []string{"fake-namespce"}
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CronJobOnly(t *testing.T) {
|
||||
|
||||
controllers := engine.PodControllerCronJob
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.SetAnnotations(map[string]string{
|
||||
engine.PodControllersAnnotation: controllers,
|
||||
})
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, controllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, rulePatches, expectedPatches)
|
||||
}
|
||||
|
||||
func Test_ForEachPod(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/policy/mutate/policy_mutate_pod_foreach_with_context.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].ExcludeResources.Namespaces = []string{"fake-namespce"}
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-resolve-image-containers","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"preconditions":{"all":[{"key":"{{request.operation}}","operator":"In","value":["CREATE","UPDATE"]}]},"mutate":{"foreach":[{"list":"request.object.spec.template.spec.containers","context":[{"name":"dictionary","configMap":{"name":"some-config-map","namespace":"some-namespace"}}],"patchStrategicMerge":{"spec":{"template":{"spec":{"containers":[{"image":"{{ dictionary.data.image }}","name":"{{ element.name }}"}]}}}}}]}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-resolve-image-containers","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"namespaces":["fake-namespce"]}},"preconditions":{"all":[{"key":"{{request.operation}}","operator":"In","value":["CREATE","UPDATE"]}]},"mutate":{"foreach":[{"list":"request.object.spec.jobTemplate.spec.template.spec.containers","context":[{"name":"dictionary","configMap":{"name":"some-config-map","namespace":"some-namespace"}}],"patchStrategicMerge":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"containers":[{"image":"{{ dictionary.data.image }}","name":"{{ element.name }}"}]}}}}}}}]}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CronJob_hasExclude(t *testing.T) {
|
||||
|
||||
controllers := engine.PodControllerCronJob
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.SetAnnotations(map[string]string{
|
||||
engine.PodControllersAnnotation: controllers,
|
||||
})
|
||||
|
||||
rule := policy.Spec.Rules[0].DeepCopy()
|
||||
rule.ExcludeResources.Kinds = []string{"Pod"}
|
||||
rule.ExcludeResources.Namespaces = []string{"test"}
|
||||
policy.Spec.Rules[0] = *rule
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, controllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"exclude":{"resources":{"kinds":["CronJob"],"namespaces":["test"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, rulePatches, expectedPatches)
|
||||
}
|
||||
|
||||
func Test_CronJobAndDeployment(t *testing.T) {
|
||||
controllers := strings.Join([]string{engine.PodControllerCronJob, "Deployment"}, ",")
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/disallow_bind_mounts.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.SetAnnotations(map[string]string{
|
||||
engine.PodControllersAnnotation: controllers,
|
||||
})
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, controllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-validate-hostPath","match":{"resources":{"kinds":["Deployment"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-validate-hostPath","match":{"resources":{"kinds":["CronJob"]}},"validate":{"message":"Host path volumes are not allowed","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"=(volumes)":[{"X(hostPath)":"null"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, rulePatches, expectedPatches)
|
||||
}
|
||||
|
||||
func Test_getControllers(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
policy []byte
|
||||
expectedControllers string
|
||||
}{
|
||||
{
|
||||
name: "rule-with-match-name",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["Namespace"],"name":"*"}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-match-selector",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"],"selector":{"matchLabels":{"foo":"bar"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-exclude-name",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"test"}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-exclude-selector",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test-getcontrollers"},"spec":{"background":false,"rules":[{"name":"test-getcontrollers","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"selector":{"matchLabels":{"foo":"bar"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-deny",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","deny":{"conditions":[{"key":"{{request.object.metadata.labels.foo}}","operator":"Equals","value":"bar"}]}}}]}}`),
|
||||
expectedControllers: engine.PodControllers,
|
||||
},
|
||||
|
||||
{
|
||||
name: "rule-with-match-mixed-kinds-pod-podcontrollers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"preconditions":{"any":[{"key":"{{request.operation}}","operator":"Equals","value":"CREATE"}]},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-exclude-mixed-kinds-pod-podcontrollers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-match-kinds-pod-only",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","pattern":{"metadata":{"labels":{"foo":"bar"}}}}}]}}`),
|
||||
expectedControllers: engine.PodControllers,
|
||||
},
|
||||
{
|
||||
name: "rule-with-exclude-kinds-pod-only",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"kinds":["Pod"],"namespaces":["test"]}},"validate":{"message":"testpolicy","pattern":{"metadata":{"labels":{"foo":"bar"}}}}}]}}`),
|
||||
expectedControllers: engine.PodControllers,
|
||||
},
|
||||
{
|
||||
name: "rule-with-mutate-patches",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["Pod"]}},"mutate":{"patchesJson6902":"-op:add\npath:/spec/containers/0/env/-1\nvalue:{\"name\":\"SERVICE\",\"value\":{{request.object.spec.template.metadata.labels.app}}}"}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-generate",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"add-networkpolicy"},"spec":{"rules":[{"name":"default-deny-ingress","match":{"resources":{"kinds":["Namespace"],"name":"*"}},"exclude":{"resources":{"namespaces":["kube-system","default","kube-public","kyverno"]}},"generate":{"kind":"NetworkPolicy","name":"default-deny-ingress","namespace":"{{request.object.metadata.name}}","synchronize":true,"data":{"spec":{"podSelector":{},"policyTypes":["Ingress"]}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-predefined-invalid-controllers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"DaemonSet,Deployment,StatefulSet","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-predefined-valid-controllers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Pod","Deployment"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
{
|
||||
name: "rule-with-only-predefined-valid-controllers",
|
||||
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-service-labels-env"},"annotations":null,"pod-policies.kyverno.io/autogen-controllers":"none","spec":{"background":false,"rules":[{"name":"set-service-label","match":{"resources":{"kinds":["Namespace"]}},"mutate":{"patchStrategicMerge":{"metadata":{"labels":{"+(service)":"{{request.object.spec.template.metadata.labels.app}}"}}}}}]}}`),
|
||||
expectedControllers: "none",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
var policy kyverno.ClusterPolicy
|
||||
err := json.Unmarshal(test.policy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
applyAutoGen, controllers := CanAutoGen(&policy.Spec, log.Log)
|
||||
if !applyAutoGen {
|
||||
controllers = "none"
|
||||
}
|
||||
assert.Equal(t, test.expectedControllers, controllers, fmt.Sprintf("test %s failed", test.name))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UpdateVariablePath(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/best_practices/select-secrets.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-select-secrets-from-volumes","match":{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}},"context":[{"name":"volsecret","apiCall":{"urlPath":"/api/v1/namespaces/{{request.object.spec.template.metadata.namespace}}/secrets/{{request.object.spec.template.spec.volumes[0].secret.secretName}}","jmesPath":"metadata.labels.foo"}}],"preconditions":[{"key":"{{ request.operation }}","operator":"Equals","value":"CREATE"}],"validate":{"message":"The Secret named {{request.object.spec.template.spec.volumes[0].secret.secretName}} is restricted and may not be used.","pattern":{"spec":{"template":{"spec":{"containers":[{"image":"registry.domain.com/*"}]}}}}}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-select-secrets-from-volumes","match":{"resources":{"kinds":["CronJob"]}},"context":[{"name":"volsecret","apiCall":{"urlPath":"/api/v1/namespaces/{{request.object.spec.template.metadata.namespace}}/secrets/{{request.object.spec.jobTemplate.spec.template.spec.volumes[0].secret.secretName}}","jmesPath":"metadata.labels.foo"}}],"preconditions":[{"key":"{{ request.operation }}","operator":"Equals","value":"CREATE"}],"validate":{"message":"The Secret named {{request.object.spec.jobTemplate.spec.template.spec.volumes[0].secret.secretName}} is restricted and may not be used.","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"spec":{"containers":[{"image":"registry.domain.com/*"}]}}}}}}}}}`),
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, rulePatches, expectedPatches)
|
||||
}
|
||||
|
||||
func Test_checkForGVKFormatPatch(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
@ -466,41 +101,3 @@ func Test_checkForGVKFormatPatch(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Deny(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
baseDir := filepath.Dir(filepath.Dir(dir))
|
||||
assert.NilError(t, err)
|
||||
file, err := ioutil.ReadFile(baseDir + "/test/policy/deny/policy.yaml")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
policies, err := utils.GetPolicy(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
policy := policies[0]
|
||||
policy.Spec.Rules[0].MatchResources.Any = kyverno.ResourceFilters{
|
||||
{
|
||||
ResourceDescription: kyverno.ResourceDescription{
|
||||
Kinds: []string{"Pod"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rulePatches, errs := generateRulePatches(&policy.Spec, engine.PodControllers, log.Log)
|
||||
fmt.Println("utils.JoinPatches(patches)erterter", string(utils.JoinPatches(rulePatches)))
|
||||
if len(errs) != 0 {
|
||||
t.Log(errs)
|
||||
}
|
||||
expectedPatches := [][]byte{
|
||||
[]byte(`{"path":"/spec/rules/1","op":"add","value":{"name":"autogen-disallow-mount-containerd-sock","match":{"any":[{"resources":{"kinds":["DaemonSet","Deployment","Job","StatefulSet"]}}],"resources":{"kinds":["Pod"]}},"validate":{"foreach":[{"list":"request.object.spec.template.spec.volumes[]","deny":{"conditions":{"any":[{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/var/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"\\var\\run\\containerd\\containerd.sock"}]}}}]}}}`),
|
||||
[]byte(`{"path":"/spec/rules/2","op":"add","value":{"name":"autogen-cronjob-disallow-mount-containerd-sock","match":{"any":[{"resources":{"kinds":["CronJob"]}}],"resources":{"kinds":["Pod"]}},"validate":{"foreach":[{"list":"request.object.spec.jobTemplate.spec.template.spec.volumes[]","deny":{"conditions":{"any":[{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/var/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"/run/containerd/containerd.sock"},{"key":"{{ path_canonicalize(element.hostPath.path) }}","operator":"Equals","value":"\\var\\run\\containerd\\containerd.sock"}]}}}]}}}`),
|
||||
}
|
||||
|
||||
for i, ep := range expectedPatches {
|
||||
assert.Equal(t, string(rulePatches[i]), string(ep),
|
||||
fmt.Sprintf("unexpected patch: %s\nexpected: %s", rulePatches[i], ep))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue