1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-18 02:06:52 +00:00

Disable auto-gen when a rule has mixed of kinds: pod & pod controllers ()

* disable auto-gen when a rule has mixed of kinds: pod & pod controllers

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* Bugfix :  Make match.resources.kinds required ()

* Fix Dev setup

* make kind required in MatchResources

* add test cases

Co-authored-by: vyankatesh <vyankatesh@neualto.com>

* address PR comments

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* update background canAutoGen unit tests

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

Co-authored-by: Vyankatesh Kudtarkar <vyankateshkd@gmail.com>
Co-authored-by: vyankatesh <vyankatesh@neualto.com>
This commit is contained in:
shuting 2021-04-29 14:59:37 -07:00 committed by GitHub
parent 34af7a930c
commit 618a69961e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 65 deletions

View file

@ -197,7 +197,7 @@ func (pc *PolicyController) addPolicy(obj interface{}) {
logger.Info("policy created", "uid", p.UID, "kind", "ClusterPolicy", "name", p.Name)
if p.Spec.Background == nil || p.Spec.ValidationFailureAction == "" || checkAutoGenRules(p, logger) {
if p.Spec.Background == nil || p.Spec.ValidationFailureAction == "" || missingAutoGenRules(p, logger) {
pol, _ := common.MutatePolicy(p, logger)
p.SetGroupVersionKind(schema.GroupVersionKind{Group: "kyverno.io", Version: "v1", Kind: "ClusterPolicy"})
_, err := pc.client.UpdateResource("kyverno.io/v1", "ClusterPolicy", "", pol, false)
@ -219,7 +219,7 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
oldP := old.(*kyverno.ClusterPolicy)
curP := cur.(*kyverno.ClusterPolicy)
if curP.Spec.Background == nil || curP.Spec.ValidationFailureAction == "" || checkAutoGenRules(curP, logger) {
if curP.Spec.Background == nil || curP.Spec.ValidationFailureAction == "" || missingAutoGenRules(curP, logger) {
pol, _ := common.MutatePolicy(curP, logger)
curP.SetGroupVersionKind(schema.GroupVersionKind{Group: "kyverno.io", Version: "v1", Kind: "ClusterPolicy"})
_, err := pc.client.UpdateResource("kyverno.io/v1", "ClusterPolicy", "", pol, false)
@ -274,7 +274,7 @@ func (pc *PolicyController) addNsPolicy(obj interface{}) {
logger.Info("policy created", "uid", p.UID, "kind", "Policy", "name", p.Name, "namespaces", p.Namespace)
pol := ConvertPolicyToClusterPolicy(p)
if pol.Spec.Background == nil || pol.Spec.ValidationFailureAction == "" || checkAutoGenRules(pol, logger) {
if pol.Spec.Background == nil || pol.Spec.ValidationFailureAction == "" || missingAutoGenRules(pol, logger) {
nsPol, _ := common.MutatePolicy(pol, logger)
pol.SetGroupVersionKind(schema.GroupVersionKind{Group: "kyverno.io", Version: "v1", Kind: "Policy"})
_, err := pc.client.UpdateResource("kyverno.io/v1", "Policy", p.Namespace, nsPol, false)
@ -295,7 +295,7 @@ func (pc *PolicyController) updateNsPolicy(old, cur interface{}) {
curP := cur.(*kyverno.Policy)
ncurP := ConvertPolicyToClusterPolicy(curP)
if ncurP.Spec.Background == nil || ncurP.Spec.ValidationFailureAction == "" || checkAutoGenRules(ncurP, logger) {
if ncurP.Spec.Background == nil || ncurP.Spec.ValidationFailureAction == "" || missingAutoGenRules(ncurP, logger) {
nsPol, _ := common.MutatePolicy(ncurP, logger)
ncurP.SetGroupVersionKind(schema.GroupVersionKind{Group: "kyverno.io", Version: "v1", Kind: "Policy"})
_, err := pc.client.UpdateResource("kyverno.io/v1", "Policy", ncurP.GetNamespace(), nsPol, false)
@ -520,14 +520,15 @@ func updateGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList [
}
}
func checkAutoGenRules(policy *kyverno.ClusterPolicy, log logr.Logger) bool {
func missingAutoGenRules(policy *kyverno.ClusterPolicy, log logr.Logger) bool {
var podRuleName []string
ruleCount := 1
if pm.GetControllers(policy, log) != "none" {
if canApplyAutoGen, _ := pm.CanAutoGen(policy, log); canApplyAutoGen {
for _, rule := range policy.Spec.Rules {
podRuleName = append(podRuleName, rule.Name)
}
}
if len(podRuleName) > 0 {
annotations := policy.GetAnnotations()
val, ok := annotations["pod-policies.kyverno.io/autogen-controllers"]

View file

@ -1293,40 +1293,25 @@ func Test_checkAutoGenRules(t *testing.T) {
expectedResult bool
}{
{
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":"*"}}}]}}`),
expectedResult: false,
},
{
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"}}}}}]}}`),
expectedResult: false,
},
{
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"}}}]}}`),
expectedResult: false,
},
{
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"}}}}}]}}`),
expectedResult: false,
},
{
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"}]}}}]}}`),
expectedResult: false,
},
{
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"}}}}}]}}`),
name: "rule-missing-autogen-cronjob",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test","annotations":{"pod-policies.kyverno.io/autogen-controllers":"Deployment,CronJob"}},"spec":{"rules":[{"match":{"resources":{"kinds":["Pod"]}},"name":"block-old-flux","validate":{"message":"CannotuseoldFluxv1annotation.","pattern":{"metadata":{"=(annotations)":{"X(fluxcd.io/*)":"*?"}}}}},{"match":{"resources":{"kinds":["Deployment"]}},"name":"autogen-block-old-flux","validate":{"message":"CannotuseoldFluxv1annotation.","pattern":{"spec":{"template":{"metadata":{"=(annotations)":{"X(fluxcd.io/*)":"*?"}}}}}}}]}}`),
expectedResult: true,
},
{
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"}}}}}]}}`),
name: "rule-missing-autogen-deployment",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test","annotations":{"pod-policies.kyverno.io/autogen-controllers":"Deployment,CronJob"}},"spec":{"rules":[{"match":{"resources":{"kinds":["Pod"]}},"name":"block-old-flux","validate":{"message":"CannotuseoldFluxv1annotation.","pattern":{"metadata":{"=(annotations)":{"X(fluxcd.io/*)":"*?"}}}}},{"match":{"resources":{"kinds":["CronJob"]}},"name":"autogen-cronjob-block-old-flux","validate":{"message":"CannotuseoldFluxv1annotation.","pattern":{"spec":{"jobTemplate":{"spec":{"template":{"metadata":{"=(annotations)":{"X(fluxcd.io/*)":"*?"}}}}}}}}}]}}`),
expectedResult: true,
},
{
name: "rule-missing-autogen-all",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test","annotations":{"pod-policies.kyverno.io/autogen-controllers":"Deployment,CronJob,StatefulSet,Job,DaemonSet"}},"spec":{"rules":[{"match":{"resources":{"kinds":["Pod"]}},"name":"block-old-flux","validate":{"message":"CannotuseoldFluxv1annotation.","pattern":{"metadata":{"=(annotations)":{"X(fluxcd.io/*)":"*?"}}}}}]}}`),
expectedResult: true,
},
{
name: "rule-with-autogen-disabled",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test","annotations":{"pod-policies.kyverno.io/autogen-controllers":"none"}},"spec":{"rules":[{"match":{"resources":{"kinds":["Pod"]}},"name":"block-old-flux","validate":{"message":"CannotuseoldFluxv1annotation.","pattern":{"metadata":{"=(annotations)":{"X(fluxcd.io/*)":"*?"}}}}}]}}`),
expectedResult: false,
},
}
for _, test := range testCases {
@ -1334,7 +1319,7 @@ func Test_checkAutoGenRules(t *testing.T) {
err := json.Unmarshal(test.policy, &policy)
assert.NilError(t, err)
res := checkAutoGenRules(&policy, log.Log)
res := missingAutoGenRules(&policy, log.Log)
assert.Equal(t, test.expectedResult, res, fmt.Sprintf("test %s failed", test.name))
}
}

View file

@ -219,40 +219,50 @@ func defaultvalidationFailureAction(policy *kyverno.ClusterPolicy, log logr.Logg
// GeneratePodControllerRule returns two patches: rulePatches and annotation patch(if necessary)
func GeneratePodControllerRule(policy kyverno.ClusterPolicy, log logr.Logger) (patches [][]byte, errs []error) {
ann := policy.GetAnnotations()
controllers, ok := ann[engine.PodControllersAnnotation]
applyAutoGen, desiredControllers := CanAutoGen(&policy, log)
// scenario A
if !ok {
controllers = engine.PodControllers
annPatch, err := defaultPodControllerAnnotation(ann)
ann := policy.GetAnnotations()
actualControllers, ok := ann[engine.PodControllersAnnotation]
// - scenario A
// - predefined controllers are invalid, overwrite the value
if !ok || !applyAutoGen {
actualControllers = desiredControllers
annPatch, err := defaultPodControllerAnnotation(ann, actualControllers)
if err != nil {
errs = append(errs, fmt.Errorf("failed to generate pod controller annotation for policy '%s': %v", policy.Name, err))
} else {
patches = append(patches, annPatch)
}
} else {
fmt.Println("===applyAutoGen", applyAutoGen)
if !applyAutoGen {
actualControllers = desiredControllers
fmt.Println("===expected ", actualControllers)
}
}
// scenario B
if controllers == "none" {
return nil, nil
if actualControllers == "none" {
return patches, nil
}
log.V(3).Info("auto generating rule for pod controllers", "controllers", controllers)
log.V(3).Info("auto generating rule for pod controllers", "controllers", actualControllers)
p, err := generateRulePatches(policy, controllers, log)
p, err := generateRulePatches(policy, actualControllers, log)
patches = append(patches, p...)
errs = append(errs, err...)
return
}
// getControllers gets applicable pod controllers, it returns
// CanAutoGen checks whether the rule(s) (in policy) can be applied to Pod controllers
// returns controllers as:
// - "none" if:
// - name or selector is defined
// - mixed kinds (Pod + pod controller) is defined
// - mutate.Patches/mutate.PatchesJSON6902/validate.deny/generate rule is defined
// - otherwise it returns all pod controllers
func GetControllers(policy *kyverno.ClusterPolicy, log logr.Logger) string {
func CanAutoGen(policy *kyverno.ClusterPolicy, log logr.Logger) (applyAutoGen bool, controllers string) {
for _, rule := range policy.Spec.Rules {
match := rule.MatchResources
exclude := rule.ExcludeResources
@ -260,21 +270,21 @@ func GetControllers(policy *kyverno.ClusterPolicy, log logr.Logger) string {
if match.ResourceDescription.Name != "" || match.ResourceDescription.Selector != nil ||
exclude.ResourceDescription.Name != "" || exclude.ResourceDescription.Selector != nil {
log.Info("skip generating rule on pod controllers: Name / Selector in resource description may not be applicable.", "rule", rule.Name)
return "none"
return false, "none"
}
if (len(match.Kinds) > 1 && utils.ContainsString(match.Kinds, "Pod")) ||
(len(exclude.Kinds) > 1 && utils.ContainsString(exclude.Kinds, "Pod")) {
return "none"
return false, "none"
}
if rule.Mutation.Patches != nil || rule.Mutation.PatchesJSON6902 != "" ||
rule.Validation.Deny != nil || rule.HasGenerate() {
return "none"
return false, "none"
}
}
return engine.PodControllers
return true, engine.PodControllers
}
func createRuleMap(rules []kyverno.Rule) map[string]kyvernoRule {
@ -429,10 +439,6 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.
return kyvernoRule{}
}
if rule.Mutation.Overlay == nil && !rule.HasValidate() && rule.Mutation.PatchStrategicMerge == nil {
return kyvernoRule{}
}
// Support backwards compatibility
skipAutoGeneration := false
var controllersValidated []string
@ -451,11 +457,6 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.
}
if skipAutoGeneration {
if match.ResourceDescription.Name != "" || match.ResourceDescription.Selector != nil ||
exclude.ResourceDescription.Name != "" || exclude.ResourceDescription.Selector != nil {
logger.Info("skip generating rule on pod controllers: Name / Selector in resource description may not be applicable.", "rule", rule.Name)
return kyvernoRule{}
}
if controllers == "all" {
controllers = "DaemonSet,Deployment,Job,StatefulSet"
} else {
@ -565,11 +566,11 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.
}
// defaultPodControllerAnnotation inserts an annotation
// "pod-policies.kyverno.io/autogen-controllers=DaemonSet,Deployment,Job,StatefulSet" to policy
func defaultPodControllerAnnotation(ann map[string]string) ([]byte, error) {
// "pod-policies.kyverno.io/autogen-controllers=<controllers>" to policy
func defaultPodControllerAnnotation(ann map[string]string, controllers string) ([]byte, error) {
if ann == nil {
ann = make(map[string]string)
ann[engine.PodControllersAnnotation] = engine.PodControllers
ann[engine.PodControllersAnnotation] = controllers
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
@ -594,7 +595,7 @@ func defaultPodControllerAnnotation(ann map[string]string) ([]byte, error) {
}{
"/metadata/annotations/pod-policies.kyverno.io~1autogen-controllers",
"add",
engine.PodControllers,
controllers,
}
patchByte, err := json.Marshal(jsonPatch)

View file

@ -1,12 +1,15 @@
package policymutation
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/utils"
"gotest.tools/assert"
@ -152,3 +155,87 @@ func Test_CronJobAndDeployment(t *testing.T) {
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: "none",
},
{
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",
},
}
for _, test := range testCases {
var policy kyverno.ClusterPolicy
err := json.Unmarshal(test.policy, &policy)
assert.NilError(t, err)
_, controllers := CanAutoGen(&policy, log.Log)
assert.Equal(t, test.expectedControllers, controllers, fmt.Sprintf("test %s failed", test.name))
}
}