1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

Updated autogenv2 package (#11212)

Signed-off-by: utsab818 <utsabsapkota4231@gmail.com>
This commit is contained in:
Utsab Sapkota 2024-10-04 14:43:13 +05:45 committed by GitHub
parent cbd2005775
commit a5858cbb64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 618 additions and 0 deletions

200
pkg/autogenv2/autogen.go Normal file
View file

@ -0,0 +1,200 @@
package autogenv2
import (
"fmt"
"sort"
"strings"
"github.com/kyverno/kyverno/api/kyverno"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/sets"
)
const (
// PodControllerCronJob represent CronJob string
PodControllerCronJob = "CronJob"
)
var (
PodControllers = sets.New("DaemonSet", "Deployment", "Job", "StatefulSet", "ReplicaSet", "ReplicationController", "CronJob")
podControllersKindsSet = PodControllers.Union(sets.New("Pod"))
)
// AutogenV2 defines the interface for the new autogeneration strategy.
type AutogenV2 interface {
ExtractPodSpec(resource unstructured.Unstructured) (*unstructured.Unstructured, error)
}
// ImplAutogenV2 is the implementation of the AutogenV2 interface.
type ImplAutogenV2 struct{}
// NewAutogenV2 creates a new instance of AutogenV2.
func NewAutogenV2() AutogenV2 {
return &ImplAutogenV2{}
}
func splitKinds(controllers, separator string) []string {
kinds := strings.Split(controllers, separator)
sort.Strings(kinds)
return kinds
}
func isKindOtherthanPod(kinds []string) bool {
if len(kinds) > 1 && kubeutils.ContainsKind(kinds, "Pod") {
return true
}
return false
}
func checkAutogenSupport(needed *bool, subjects ...kyvernov1.ResourceDescription) bool {
for _, subject := range subjects {
if subject.Name != "" || len(subject.Names) > 0 || subject.Selector != nil || subject.Annotations != nil || isKindOtherthanPod(subject.Kinds) {
return false
}
if needed != nil {
*needed = *needed || podControllersKindsSet.HasAny(subject.Kinds...)
}
}
return true
}
// 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 *kyvernov1.Spec) (applyAutoGen bool, controllers sets.Set[string]) {
needed := false
for _, rule := range spec.Rules {
if rule.HasGenerate() {
return false, sets.New("none")
}
if rule.Mutation != nil {
if rule.Mutation.PatchesJSON6902 != "" {
return false, sets.New("none")
}
for _, foreach := range rule.Mutation.ForEachMutation {
if foreach.PatchesJSON6902 != "" {
return false, sets.New("none")
}
}
}
match := rule.MatchResources
if !checkAutogenSupport(&needed, match.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in resource description may not be applicable.", "rule", rule.Name)
return false, sets.New[string]()
}
for _, value := range match.Any {
if !checkAutogenSupport(&needed, value.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in match any block is not applicable.", "rule", rule.Name)
return false, sets.New[string]()
}
}
for _, value := range match.All {
if !checkAutogenSupport(&needed, value.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in match all block is not applicable.", "rule", rule.Name)
return false, sets.New[string]()
}
}
if exclude := rule.ExcludeResources; exclude != nil {
if !checkAutogenSupport(&needed, exclude.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in resource description may not be applicable.", "rule", rule.Name)
return false, sets.New[string]()
}
for _, value := range exclude.Any {
if !checkAutogenSupport(&needed, value.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in exclude any block is not applicable.", "rule", rule.Name)
return false, sets.New[string]()
}
}
for _, value := range exclude.All {
if !checkAutogenSupport(&needed, value.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in exclud all block is not applicable.", "rule", rule.Name)
return false, sets.New[string]()
}
}
}
}
if !needed {
return false, sets.New[string]()
}
return true, PodControllers
}
func GetSupportedControllers(spec *kyvernov1.Spec) sets.Set[string] {
apply, controllers := CanAutoGen(spec)
if !apply || (controllers.Len() == 1 && controllers.Has("none")) {
return nil
}
return controllers
}
// GetRequestedControllers returns the requested autogen controllers based on object annotations.
func GetRequestedControllers(meta *metav1.ObjectMeta) sets.Set[string] {
annotations := meta.GetAnnotations()
if annotations == nil {
return nil
}
controllers, ok := annotations[kyverno.AnnotationAutogenControllers]
if !ok || controllers == "" {
return nil
}
if controllers == "none" {
return sets.New[string]()
}
return sets.New(splitKinds(controllers, ",")...)
}
// GetControllers computes the autogen controllers that should be applied to a policy.
// It returns the requested, supported and effective controllers (intersection of requested and supported ones).
func GetControllers(meta *metav1.ObjectMeta, spec *kyvernov1.Spec) ([]string, []string, []string) {
// compute supported and requested controllers
supported, requested := GetSupportedControllers(spec), GetRequestedControllers(meta)
// no specific request, we can return supported controllers without further filtering
if requested == nil {
return requested.UnsortedList(), supported.UnsortedList(), supported.UnsortedList()
}
// filter supported controllers, keeping only those that have been requested
var activated []string
for _, controller := range supported.UnsortedList() {
if requested.Has(controller) {
activated = append(activated, controller)
}
}
return requested.UnsortedList(), supported.UnsortedList(), activated
}
// ExtractPodSpec extracts the PodSpec from an unstructured resource if the controller supports autogen.
func (a *ImplAutogenV2) ExtractPodSpec(resource unstructured.Unstructured) (*unstructured.Unstructured, error) {
kind := resource.GetKind()
var podSpec map[string]interface{}
var found bool
var err error
switch kind {
case "Deployment", "StatefulSet", "DaemonSet", "Job", "ReplicaSet", "ReplicationController":
podSpec, found, err = unstructured.NestedMap(resource.Object, "spec", "template", "spec")
if err != nil || !found {
return nil, fmt.Errorf("error extracting pod spec: %v", err)
}
case "CronJob":
jobTemplate, found, err := unstructured.NestedMap(resource.Object, "spec", "jobTemplate", "spec", "template", "spec")
if err != nil || !found {
return nil, fmt.Errorf("error extracting pod spec from CronJob: %v", err)
}
podSpec = jobTemplate
default:
return nil, nil // No pod spec for this kind of resource
}
return &unstructured.Unstructured{Object: podSpec}, nil
}

View file

@ -0,0 +1,410 @@
package autogenv2
import (
"encoding/json"
"fmt"
"testing"
"github.com/kyverno/kyverno/api/kyverno"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/sets"
)
func Test_CanAutoGen(t *testing.T) {
testCases := []struct {
name string
policy []byte
expectedControllers sets.Set[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: sets.New("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: sets.New("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: sets.New("none"),
},
{
name: "rule-with-exclude-names",
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":{"names":["test"]}}}]}}`),
expectedControllers: sets.New("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: sets.New("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: 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: sets.New("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: sets.New("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: 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: 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: sets.New("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: sets.New("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: sets.New("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: sets.New("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: sets.New("none"),
},
{
name: "rule-with-match-kinds-pod-only-validate-exclude",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","podSecurity": {"level": "baseline","version":"v1.24","exclude":[{"controlName":"SELinux","restrictedField":"spec.containers[*].securityContext.seLinuxOptions.role","images":["nginx"],"values":["baz"]}, {"controlName":"SELinux","restrictedField":"spec.initContainers[*].securityContext.seLinuxOptions.role","images":["nodejs"],"values":["init-baz"]}]}}}]}}`),
expectedControllers: PodControllers,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
var policy kyvernov1.ClusterPolicy
err := json.Unmarshal(test.policy, &policy)
assert.NilError(t, err)
applyAutoGen, controllers := CanAutoGen(&policy.Spec)
if !applyAutoGen {
controllers = sets.New("none")
}
equalityTest := test.expectedControllers.Equal(controllers)
assert.Assert(t, equalityTest, fmt.Sprintf("expected: %v, got: %v", test.expectedControllers, controllers))
})
}
}
func Test_GetSupportedControllers(t *testing.T) {
testCases := []struct {
name string
policy []byte
expectedControllers sets.Set[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: sets.New[string](),
},
{
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: sets.New[string](),
},
{
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: sets.New[string](),
},
{
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: sets.New[string](),
},
{
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: 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: sets.New[string](),
},
{
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: sets.New[string](),
},
{
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: 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: 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: sets.New[string](),
},
{
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: sets.New[string](),
},
{
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: sets.New[string](),
},
{
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: sets.New[string](),
},
{
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: sets.New[string](),
},
{
name: "rule-with-match-kinds-pod-only-validate-exclude",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"test"},"spec":{"rules":[{"name":"require-network-policy","match":{"resources":{"kinds":["Pod"]}},"validate":{"message":"testpolicy","podSecurity": {"level": "baseline","version":"v1.24","exclude":[{"controlName":"SELinux","restrictedField":"spec.containers[*].securityContext.seLinuxOptions.role","images":["nginx"],"values":["baz"]}, {"controlName":"SELinux","restrictedField":"spec.initContainers[*].securityContext.seLinuxOptions.role","images":["nodejs"],"values":["init-baz"]}]}}}]}}`),
expectedControllers: PodControllers,
},
{
name: "rule-with-validate-podsecurity",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"pod-security"},"spec":{"rules":[{"name":"restricted","match":{"all":[{"resources":{"kinds":["Pod"]}}]},"validate":{"failureAction":"enforce","podSecurity":{"level":"restricted","version":"v1.24"}}}]}}`),
expectedControllers: PodControllers,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
var policy kyvernov1.ClusterPolicy
err := json.Unmarshal(test.policy, &policy)
assert.NilError(t, err)
controllers := GetSupportedControllers(&policy.Spec)
equalityTest := test.expectedControllers.Equal(controllers)
assert.Assert(t, equalityTest, fmt.Sprintf("expected: %v, got: %v", test.expectedControllers, controllers))
})
}
}
func Test_GetRequestedControllers(t *testing.T) {
testCases := []struct {
name string
meta metav1.ObjectMeta
expectedControllers sets.Set[string]
}{
{
name: "annotations-nil",
meta: metav1.ObjectMeta{},
expectedControllers: nil,
},
{
name: "annotation-not-set",
meta: metav1.ObjectMeta{Annotations: map[string]string{}},
expectedControllers: nil,
},
{
name: "annotation-empty",
meta: metav1.ObjectMeta{Annotations: map[string]string{kyverno.AnnotationAutogenControllers: ""}},
expectedControllers: nil,
},
{
name: "annotation-none",
meta: metav1.ObjectMeta{Annotations: map[string]string{kyverno.AnnotationAutogenControllers: "none"}},
expectedControllers: sets.New[string](),
},
{
name: "annotation-job",
meta: metav1.ObjectMeta{Annotations: map[string]string{kyverno.AnnotationAutogenControllers: "Job"}},
expectedControllers: sets.New[string]("Job"),
},
{
name: "annotation-job-deployment",
meta: metav1.ObjectMeta{Annotations: map[string]string{kyverno.AnnotationAutogenControllers: "Job,Deployment"}},
expectedControllers: sets.New[string]("Job", "Deployment"),
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
controllers := GetRequestedControllers(&test.meta)
equalityTest := test.expectedControllers.Equal(controllers)
assert.Assert(t, equalityTest, fmt.Sprintf("expected: %v, got: %v", test.expectedControllers, controllers))
})
}
}
func TestExtractPodSpec(t *testing.T) {
testCases := []struct {
name string
resource unstructured.Unstructured // The input resource
expectedPod *unstructured.Unstructured // Expected pod spec
expectError bool // Whether an error is expected
}{
{
name: "extract pod spec from deployment",
resource: unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "test-deployment",
"namespace": "default",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
},
},
},
expectedPod: &unstructured.Unstructured{
Object: map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
expectError: false,
},
{
name: "extract pod spec from cronjob",
resource: unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "batch/v1",
"kind": "CronJob",
"metadata": map[string]interface{}{
"name": "test-cronjob",
"namespace": "default",
},
"spec": map[string]interface{}{
"schedule": "* * * * *",
"jobTemplate": map[string]interface{}{
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
},
},
},
},
},
expectedPod: &unstructured.Unstructured{
Object: map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
expectError: false,
},
{
name: "no pod spec in configmap",
resource: unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "test-configmap",
"namespace": "default",
},
"data": map[string]interface{}{
"key": "value",
},
},
},
expectedPod: nil,
expectError: false,
},
{
name: "invalid resource structure",
resource: unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": nil, // missing metadata
"spec": nil, // missing spec
},
},
expectedPod: nil,
expectError: true,
},
}
autogen := NewAutogenV2()
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
// Call the function under test
podSpec, err := autogen.ExtractPodSpec(test.resource)
// Check for errors
if test.expectError {
assert.ErrorContains(t, err, "error extracting pod spec")
} else {
assert.NilError(t, err)
}
// Check for pod spec correctness
if test.expectedPod != nil {
assert.Assert(t, podSpec != nil, "expected pod spec but got nil")
assert.DeepEqual(t, test.expectedPod.Object, podSpec.Object)
} else {
assert.Assert(t, podSpec == nil, "expected nil pod spec but got a non-nil value")
}
})
}
}

8
pkg/autogenv2/log.go Normal file
View file

@ -0,0 +1,8 @@
package autogenv2
import "github.com/kyverno/kyverno/pkg/logging"
var (
logger = logging.WithName("autogen-v2")
debug = logger.V(5)
)