From 94abfaf13e145bdca2b7fe98043d665d03a45276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Thu, 22 Dec 2022 07:39:54 +0100 Subject: [PATCH] refactor: move util funcs in sub packages (#5754) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: move util func in sub packages Signed-off-by: Charles-Edouard Brétéché * fix Signed-off-by: Charles-Edouard Brétéché * Update pkg/utils/kube/crd.go Signed-off-by: shuting Signed-off-by: Charles-Edouard Brétéché Signed-off-by: shuting Co-authored-by: shuting --- cmd/initContainer/main.go | 4 +- cmd/kyverno/main.go | 4 +- pkg/background/generate/generate.go | 4 +- pkg/cosign/cosign.go | 6 +- pkg/engine/jsonutils/traverse.go | 6 +- pkg/engine/mutate/mutation.go | 4 +- pkg/engine/utils.go | 10 +- pkg/engine/validation.go | 4 +- pkg/policy/common.go | 3 +- pkg/policy/validate.go | 11 +- pkg/policycache/cache.go | 4 +- pkg/pss/evaluate.go | 8 +- pkg/userinfo/roleRef.go | 4 +- pkg/utils/data/data.go | 46 +++++ pkg/utils/data/data_test.go | 95 +++++++++++ pkg/utils/kube/crd.go | 35 ++++ pkg/utils/kube/version.go | 53 ++++++ pkg/utils/kube/version_test.go | 42 +++++ pkg/utils/util.go | 194 +-------------------- pkg/utils/util_test.go | 256 ---------------------------- pkg/utils/wildcard/utils.go | 29 ++++ pkg/utils/wildcard/utils_test.go | 145 ++++++++++++++++ pkg/webhooks/handlers/dump_test.go | 14 +- 23 files changed, 490 insertions(+), 491 deletions(-) create mode 100644 pkg/utils/data/data.go create mode 100644 pkg/utils/data/data_test.go create mode 100644 pkg/utils/kube/crd.go create mode 100644 pkg/utils/kube/version.go create mode 100644 pkg/utils/kube/version_test.go diff --git a/cmd/initContainer/main.go b/cmd/initContainer/main.go index 4e575a7e71..eeded79048 100644 --- a/cmd/initContainer/main.go +++ b/cmd/initContainer/main.go @@ -18,7 +18,7 @@ import ( "github.com/kyverno/kyverno/pkg/leaderelection" "github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/tls" - "github.com/kyverno/kyverno/pkg/utils" + kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "go.uber.org/multierr" admissionv1 "k8s.io/api/admission/v1" coordinationv1 "k8s.io/api/coordination/v1" @@ -53,7 +53,7 @@ func main() { kyvernoClient := internal.CreateKyvernoClient(logger) client := internal.CreateDClient(logger, ctx, dynamicClient, kubeClient, 15*time.Minute) // Exit for unsupported version of kubernetes cluster - if !utils.HigherThanKubernetesVersion(kubeClient.Discovery(), logging.GlobalLogger(), 1, 16, 0) { + if !kubeutils.HigherThanKubernetesVersion(kubeClient.Discovery(), logging.GlobalLogger(), 1, 16, 0) { os.Exit(1) } requests := []request{ diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 730fba56ba..74aea81c14 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -45,7 +45,7 @@ import ( "github.com/kyverno/kyverno/pkg/registryclient" "github.com/kyverno/kyverno/pkg/tls" "github.com/kyverno/kyverno/pkg/toggle" - "github.com/kyverno/kyverno/pkg/utils" + kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime" "github.com/kyverno/kyverno/pkg/webhooks" webhooksexception "github.com/kyverno/kyverno/pkg/webhooks/exception" @@ -99,7 +99,7 @@ func showWarnings(logger logr.Logger) { } func sanityChecks(dynamicClient dclient.Interface) error { - if !utils.CRDsInstalled(dynamicClient.Discovery()) { + if !kubeutils.CRDsInstalled(dynamicClient.Discovery()) { return fmt.Errorf("CRDs not installed") } return nil diff --git a/pkg/background/generate/generate.go b/pkg/background/generate/generate.go index 1933cdbc41..d210a6bc9f 100644 --- a/pkg/background/generate/generate.go +++ b/pkg/background/generate/generate.go @@ -28,7 +28,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/event" "github.com/kyverno/kyverno/pkg/registryclient" - kyvernoutils "github.com/kyverno/kyverno/pkg/utils" + datautils "github.com/kyverno/kyverno/pkg/utils/data" engineutils "github.com/kyverno/kyverno/pkg/utils/engine" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "golang.org/x/exp/slices" @@ -595,7 +595,7 @@ func newGenResource(genAPIVersion, genKind, genNamespace, genName string) kyvern } func manageData(log logr.Logger, apiVersion, kind, namespace, name string, data interface{}, synchronize bool, ur kyvernov1beta1.UpdateRequest, client dclient.Interface) (map[string]interface{}, ResourceMode, error) { - resource, err := kyvernoutils.ToMap(data) + resource, err := datautils.ToMap(data) if err != nil { return nil, Skip, err } diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index 834717282a..799f2ef237 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -14,7 +14,7 @@ import ( "github.com/in-toto/in-toto-golang/in_toto" "github.com/kyverno/kyverno/pkg/registryclient" "github.com/kyverno/kyverno/pkg/tracing" - "github.com/kyverno/kyverno/pkg/utils" + datautils "github.com/kyverno/kyverno/pkg/utils/data" wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard" "github.com/pkg/errors" "github.com/sigstore/cosign/cmd/cosign/cli/fulcio" @@ -404,7 +404,7 @@ func decodePayload(payloadBase64 string) (map[string]interface{}, error) { // - in_toto.PredicateLinkV1 // - in_toto.PredicateSPDX // any other custom predicate - return utils.ToMap(statement) + return datautils.ToMap(statement) } return decodeCosignCustomProvenanceV01(statement) @@ -432,7 +432,7 @@ func decodeCosignCustomProvenanceV01(statement in_toto.Statement) (map[string]in statement.Predicate = predicate } - return utils.ToMap(statement) + return datautils.ToMap(statement) } func stringToJSONMap(i interface{}) (map[string]interface{}, error) { diff --git a/pkg/engine/jsonutils/traverse.go b/pkg/engine/jsonutils/traverse.go index 191d6bb783..ede2832b59 100644 --- a/pkg/engine/jsonutils/traverse.go +++ b/pkg/engine/jsonutils/traverse.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/kyverno/kyverno/pkg/utils" + datautils "github.com/kyverno/kyverno/pkg/utils/data" "golang.org/x/exp/slices" ) @@ -71,13 +71,13 @@ func (t *Traversal) traverseJSON(element interface{}, path string) (interface{}, // traverse further switch typed := element.(type) { case map[string]interface{}: - return t.traverseObject(utils.CopyMap(typed), path) + return t.traverseObject(datautils.CopyMap(typed), path) case []interface{}: return t.traverseList(slices.Clone(typed), path) case []map[string]interface{}: - return t.traverseList(utils.CopySliceOfMaps(typed), path) + return t.traverseList(datautils.CopySliceOfMaps(typed), path) case Key: return typed.Key, nil diff --git a/pkg/engine/mutate/mutation.go b/pkg/engine/mutate/mutation.go index 0b2ff077ff..aa0b9a99a4 100644 --- a/pkg/engine/mutate/mutation.go +++ b/pkg/engine/mutate/mutation.go @@ -10,7 +10,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/mutate/patch" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/variables" - "github.com/kyverno/kyverno/pkg/utils" + datautils "github.com/kyverno/kyverno/pkg/utils/data" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -91,7 +91,7 @@ func ForEach(name string, foreach kyvernov1.ForEachMutation, ctx context.Interfa } func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface, logger logr.Logger) (*kyvernov1.ForEachMutation, error) { - jsonObj, err := utils.ToMap(fe) + jsonObj, err := datautils.ToMap(fe) if err != nil { return nil, err } diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index f39f4b785f..7be5363f89 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -16,7 +16,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/engine/wildcards" "github.com/kyverno/kyverno/pkg/logging" - "github.com/kyverno/kyverno/pkg/utils" + datautils "github.com/kyverno/kyverno/pkg/utils/data" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "github.com/kyverno/kyverno/pkg/utils/wildcard" "github.com/pkg/errors" @@ -210,14 +210,14 @@ func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*met keys := append(admissionInfo.AdmissionUserInfo.Groups, admissionInfo.AdmissionUserInfo.Username) var userInfoErrors []error - if len(userInfo.Roles) > 0 && !utils.SliceContains(keys, dynamicConfig...) { - if !utils.SliceContains(userInfo.Roles, admissionInfo.Roles...) { + if len(userInfo.Roles) > 0 && !datautils.SliceContains(keys, dynamicConfig...) { + if !datautils.SliceContains(userInfo.Roles, admissionInfo.Roles...) { userInfoErrors = append(userInfoErrors, fmt.Errorf("user info does not match roles for the given conditionBlock")) } } - if len(userInfo.ClusterRoles) > 0 && !utils.SliceContains(keys, dynamicConfig...) { - if !utils.SliceContains(userInfo.ClusterRoles, admissionInfo.ClusterRoles...) { + if len(userInfo.ClusterRoles) > 0 && !datautils.SliceContains(keys, dynamicConfig...) { + if !datautils.SliceContains(userInfo.ClusterRoles, admissionInfo.ClusterRoles...) { userInfoErrors = append(userInfoErrors, fmt.Errorf("user info does not match clustersRoles for the given conditionBlock")) } } diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 22570c0691..103f7b78ad 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -22,8 +22,8 @@ import ( "github.com/kyverno/kyverno/pkg/pss" "github.com/kyverno/kyverno/pkg/registryclient" "github.com/kyverno/kyverno/pkg/tracing" - "github.com/kyverno/kyverno/pkg/utils" "github.com/kyverno/kyverno/pkg/utils/api" + datautils "github.com/kyverno/kyverno/pkg/utils/data" matched "github.com/kyverno/kyverno/pkg/utils/match" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" @@ -216,7 +216,7 @@ func newValidator(log logr.Logger, rclient registryclient.Client, ctx *PolicyCon func newForEachValidator(foreach kyvernov1.ForEachValidation, rclient registryclient.Client, nesting int, rule *kyvernov1.Rule, ctx *PolicyContext, log logr.Logger) (*validator, error) { ruleCopy := rule.DeepCopy() - anyAllConditions, err := utils.ToMap(foreach.AnyAllConditions) + anyAllConditions, err := datautils.ToMap(foreach.AnyAllConditions) if err != nil { return nil, errors.Wrap(err, "failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions") } diff --git a/pkg/policy/common.go b/pkg/policy/common.go index 5d655e10c0..9381ad34b4 100644 --- a/pkg/policy/common.go +++ b/pkg/policy/common.go @@ -7,7 +7,6 @@ import ( "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/config" - "github.com/kyverno/kyverno/pkg/utils" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "github.com/kyverno/kyverno/pkg/utils/wildcard" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -94,7 +93,7 @@ func excludeResources(included map[string]unstructured.Unstructured, exclude kyv if len(exclude.Namespaces) == 0 { return NotEvaluate } - if utils.ContainsNamepace(exclude.Namespaces, namespace) { + if wildcard.CheckPatterns(exclude.Namespaces, namespace) { return Skip } return Process diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index 4f53fb2d10..67617a8eb8 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -24,6 +24,7 @@ import ( "github.com/kyverno/kyverno/pkg/openapi" "github.com/kyverno/kyverno/pkg/utils" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" + "github.com/kyverno/kyverno/pkg/utils/wildcard" "github.com/pkg/errors" "golang.org/x/exp/slices" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" @@ -1268,20 +1269,20 @@ func validateKinds(kinds []string, mock, backgroundScanningEnabled, isValidation } func validateWildcardsWithNamespaces(enforce, audit, enforceW, auditW []string) error { - pat, ns, notOk := utils.CheckWildcardNamespaces(auditW, enforce) + pat, ns, notOk := wildcard.MatchPatterns(auditW, enforce...) if notOk { return fmt.Errorf("wildcard pattern '%s' matches with namespace '%s'", pat, ns) } - pat, ns, notOk = utils.CheckWildcardNamespaces(enforceW, audit) + pat, ns, notOk = wildcard.MatchPatterns(enforceW, audit...) if notOk { return fmt.Errorf("wildcard pattern '%s' matches with namespace '%s'", pat, ns) } - pat1, pat2, notOk := utils.CheckWildcardNamespaces(auditW, enforceW) + pat1, pat2, notOk := wildcard.MatchPatterns(auditW, enforceW...) if notOk { return fmt.Errorf("wildcard pattern '%s' conflicts with the pattern '%s'", pat1, pat2) } - pat1, pat2, notOk = utils.CheckWildcardNamespaces(enforceW, auditW) + pat1, pat2, notOk = wildcard.MatchPatterns(enforceW, auditW...) if notOk { return fmt.Errorf("wildcard pattern '%s' conflicts with the pattern '%s'", pat1, pat2) } @@ -1298,7 +1299,7 @@ func validateNamespaces(s *kyvernov1.Spec, path *field.Path) error { } for i, vfa := range s.ValidationFailureActionOverrides { - patternList, nsList := utils.SeperateWildcards(vfa.Namespaces) + patternList, nsList := wildcard.SeperateWildcards(vfa.Namespaces) if vfa.Action.Audit() { if action["enforce"].HasAny(nsList...) { diff --git a/pkg/policycache/cache.go b/pkg/policycache/cache.go index 435e42fa81..942eac5c6f 100644 --- a/pkg/policycache/cache.go +++ b/pkg/policycache/cache.go @@ -2,7 +2,7 @@ package policycache import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - kyvernoutils "github.com/kyverno/kyverno/pkg/utils" + "github.com/kyverno/kyverno/pkg/utils/wildcard" ) // Cache get method use for to get policy names and mostly use to test cache testcases @@ -81,7 +81,7 @@ func checkValidationFailureActionOverrides(enforce bool, ns string, policy kyver return false } for _, action := range validationFailureActionOverrides { - if action.Action.Enforce() != enforce && kyvernoutils.ContainsNamepace(action.Namespaces, ns) { + if action.Action.Enforce() != enforce && wildcard.CheckPatterns(action.Namespaces, ns) { return false } } diff --git a/pkg/pss/evaluate.go b/pkg/pss/evaluate.go index 22e83db6dc..3c602c3a63 100644 --- a/pkg/pss/evaluate.go +++ b/pkg/pss/evaluate.go @@ -5,7 +5,7 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" pssutils "github.com/kyverno/kyverno/pkg/pss/utils" - "github.com/kyverno/kyverno/pkg/utils" + "github.com/kyverno/kyverno/pkg/utils/wildcard" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/pod-security-admission/api" @@ -126,18 +126,18 @@ func GetPodWithMatchingContainers(exclude kyvernov1.PodSecurityStandard, pod *co }, } for _, container := range pod.Spec.Containers { - if utils.ContainsWildcardPatterns(matchingImages, container.Image) { + if wildcard.CheckPatterns(matchingImages, container.Image) { matching.Spec.Containers = append(matching.Spec.Containers, container) } } for _, container := range pod.Spec.InitContainers { - if utils.ContainsWildcardPatterns(matchingImages, container.Image) { + if wildcard.CheckPatterns(matchingImages, container.Image) { matching.Spec.InitContainers = append(matching.Spec.InitContainers, container) } } for _, container := range pod.Spec.EphemeralContainers { - if utils.ContainsWildcardPatterns(matchingImages, container.Image) { + if wildcard.CheckPatterns(matchingImages, container.Image) { matching.Spec.EphemeralContainers = append(matching.Spec.EphemeralContainers, container) } } diff --git a/pkg/userinfo/roleRef.go b/pkg/userinfo/roleRef.go index 37d3ba3ecc..f0ecb7aee7 100644 --- a/pkg/userinfo/roleRef.go +++ b/pkg/userinfo/roleRef.go @@ -6,7 +6,7 @@ import ( "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/logging" - "github.com/kyverno/kyverno/pkg/utils" + datautils "github.com/kyverno/kyverno/pkg/utils/data" admissionv1 "k8s.io/api/admission/v1" authenticationv1 "k8s.io/api/authentication/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -24,7 +24,7 @@ const ( // GetRoleRef gets the list of roles and cluster roles for the incoming api-request func GetRoleRef(rbLister rbacv1listers.RoleBindingLister, crbLister rbacv1listers.ClusterRoleBindingLister, request *admissionv1.AdmissionRequest, dynamicConfig config.Configuration) ([]string, []string, error) { keys := append(request.UserInfo.Groups, request.UserInfo.Username) - if utils.SliceContains(keys, dynamicConfig.GetExcludeGroupRole()...) { + if datautils.SliceContains(keys, dynamicConfig.GetExcludeGroupRole()...) { return nil, nil, nil } // rolebindings diff --git a/pkg/utils/data/data.go b/pkg/utils/data/data.go new file mode 100644 index 0000000000..7ce87e326f --- /dev/null +++ b/pkg/utils/data/data.go @@ -0,0 +1,46 @@ +package data + +import ( + "encoding/json" + + "k8s.io/apimachinery/pkg/util/sets" +) + +// CopyMap creates a full copy of the target map +func CopyMap(m map[string]interface{}) map[string]interface{} { + mapCopy := make(map[string]interface{}) + for k, v := range m { + mapCopy[k] = v + } + return mapCopy +} + +// CopySliceOfMaps creates a full copy of the target slice +func CopySliceOfMaps(s []map[string]interface{}) []interface{} { + sliceCopy := make([]interface{}, len(s)) + for i, v := range s { + sliceCopy[i] = CopyMap(v) + } + return sliceCopy +} + +func ToMap(data interface{}) (map[string]interface{}, error) { + if m, ok := data.(map[string]interface{}); ok { + return m, nil + } + b, err := json.Marshal(data) + if err != nil { + return nil, err + } + mapData := make(map[string]interface{}) + err = json.Unmarshal(b, &mapData) + if err != nil { + return nil, err + } + return mapData, nil +} + +// SliceContains checks whether values are contained in slice +func SliceContains(slice []string, values ...string) bool { + return sets.New(slice...).HasAny(values...) +} diff --git a/pkg/utils/data/data_test.go b/pkg/utils/data/data_test.go new file mode 100644 index 0000000000..65ee82952a --- /dev/null +++ b/pkg/utils/data/data_test.go @@ -0,0 +1,95 @@ +package data + +import ( + "testing" + + "gotest.tools/assert" +) + +func TestOriginalMapMustNotBeChanged(t *testing.T) { + // no variables + originalMap := map[string]interface{}{ + "rsc": 3711, + "r": 2138, + "gri": 1908, + "adg": 912, + } + mapCopy := CopyMap(originalMap) + mapCopy["r"] = 1 + assert.Equal(t, originalMap["r"], 2138) +} + +func TestSliceContains(t *testing.T) { + type args struct { + slice []string + values []string + } + tests := []struct { + name string + args args + want bool + }{{ + name: "empty slice", + args: args{ + slice: []string{}, + values: []string{"ccc", "ddd"}, + }, + want: false, + }, { + name: "nil slice", + args: args{ + slice: nil, + values: []string{"ccc", "ddd"}, + }, + want: false, + }, { + name: "empty values", + args: args{ + slice: []string{"aaa", "bbb"}, + values: []string{}, + }, + want: false, + }, { + name: "nil values", + args: args{ + slice: []string{"aaa", "bbb"}, + values: nil, + }, + want: false, + }, { + name: "none match", + args: args{ + slice: []string{"aaa", "bbb"}, + values: []string{"ccc", "ddd"}, + }, + want: false, + }, { + name: "one match", + args: args{ + slice: []string{"aaa", "bbb"}, + values: []string{"aaa"}, + }, + want: true, + }, { + name: "one match, one doesn't match", + args: args{ + slice: []string{"aaa", "bbb"}, + values: []string{"aaa", "ddd"}, + }, + want: true, + }, { + name: "all match", + args: args{ + slice: []string{"aaa", "bbb"}, + values: []string{"aaa", "bbb"}, + }, + want: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SliceContains(tt.args.slice, tt.args.values...); got != tt.want { + t.Errorf("SliceContains() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/utils/kube/crd.go b/pkg/utils/kube/crd.go new file mode 100644 index 0000000000..305453c777 --- /dev/null +++ b/pkg/utils/kube/crd.go @@ -0,0 +1,35 @@ +package kube + +import ( + "fmt" + + "github.com/kyverno/kyverno/pkg/logging" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type disco interface { + GetGVRFromKind(string) (schema.GroupVersionResource, error) +} + +// CRDsInstalled checks if the Kyverno CRDs are installed or not +func CRDsInstalled(discovery disco) bool { + kyvernoCRDs := []string{"ClusterPolicy", "Policy", "ClusterPolicyReport", "PolicyReport", "AdmissionReport", "BackgroundScanReport", "ClusterAdmissionReport", "ClusterBackgroundScanReport"} + for _, crd := range kyvernoCRDs { + if !isCRDInstalled(discovery, crd) { + return false + } + } + return true +} + +func isCRDInstalled(discovery disco, kind string) bool { + gvr, err := discovery.GetGVRFromKind(kind) + if gvr.Empty() { + if err == nil { + err = fmt.Errorf("not found") + } + logging.Error(err, "failed to retrieve CRD", "kind", kind) + return false + } + return true +} diff --git a/pkg/utils/kube/version.go b/pkg/utils/kube/version.go new file mode 100644 index 0000000000..6c007ea3be --- /dev/null +++ b/pkg/utils/kube/version.go @@ -0,0 +1,53 @@ +package kube + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/go-logr/logr" + "k8s.io/client-go/discovery" +) + +var regexVersion = regexp.MustCompile(`v(\d+).(\d+).(\d+)\.*`) + +// HigherThanKubernetesVersion compare Kubernetes client version to user given version +func HigherThanKubernetesVersion(client discovery.ServerVersionInterface, log logr.Logger, major, minor, patch int) bool { + logger := log.WithName("CompareKubernetesVersion") + serverVersion, err := client.ServerVersion() + if err != nil { + logger.Error(err, "Failed to get kubernetes server version") + return false + } + b, err := isVersionHigher(serverVersion.String(), major, minor, patch) + if err != nil { + logger.Error(err, "serverVersion", serverVersion.String()) + return false + } + return b +} + +func isVersionHigher(version string, major int, minor int, patch int) (bool, error) { + groups := regexVersion.FindStringSubmatch(version) + if len(groups) != 4 { + return false, fmt.Errorf("invalid version %s. Expected {major}.{minor}.{patch}", version) + } + currentMajor, err := strconv.Atoi(groups[1]) + if err != nil { + return false, fmt.Errorf("failed to extract major version from %s", version) + } + currentMinor, err := strconv.Atoi(groups[2]) + if err != nil { + return false, fmt.Errorf("failed to extract minor version from %s", version) + } + currentPatch, err := strconv.Atoi(groups[3]) + if err != nil { + return false, fmt.Errorf("failed to extract minor version from %s", version) + } + if currentMajor < major || + (currentMajor == major && currentMinor < minor) || + (currentMajor == major && currentMinor == minor && currentPatch <= patch) { + return false, nil + } + return true, nil +} diff --git a/pkg/utils/kube/version_test.go b/pkg/utils/kube/version_test.go new file mode 100644 index 0000000000..69a99d0257 --- /dev/null +++ b/pkg/utils/kube/version_test.go @@ -0,0 +1,42 @@ +package kube + +import ( + "testing" + + "gotest.tools/assert" +) + +func Test_higherVersion(t *testing.T) { + v, err := isVersionHigher("invalid.version", 1, 1, 1) + assert.Assert(t, v == false && err != nil) + + v, err = isVersionHigher("invalid-version", 0, 0, 0) + assert.Assert(t, v == false && err != nil) + + v, err = isVersionHigher("v1.1.1", 1, 1, 1) + assert.Assert(t, v == false && err == nil) + + v, err = isVersionHigher("v1.0.0", 1, 1, 1) + assert.Assert(t, v == false && err == nil) + + v, err = isVersionHigher("v1.5.9", 1, 5, 8) + assert.Assert(t, v == true && err == nil) + + v, err = isVersionHigher("v1.5.9+distro", 1, 5, 8) + assert.Assert(t, v == true && err == nil) + + v, err = isVersionHigher("v1.5.9+distro", 1, 5, 8) + assert.Assert(t, v == true && err == nil) + + v, err = isVersionHigher("v1.5.9-rc2", 1, 5, 9) + assert.Assert(t, v == false && err == nil) + + v, err = isVersionHigher("v1.5.9", 2, 1, 0) + assert.Assert(t, v == false && err == nil) + + v, err = isVersionHigher("v2.1.0", 1, 5, 9) + assert.Assert(t, v == true && err == nil) + + v, err = isVersionHigher("v1.5.9-x-v1.5.9.x", 1, 5, 8) + assert.Assert(t, v == true && err == nil) +} diff --git a/pkg/utils/util.go b/pkg/utils/util.go index 24ad45646f..6729f42440 100644 --- a/pkg/utils/util.go +++ b/pkg/utils/util.go @@ -3,15 +3,11 @@ package utils import ( "encoding/json" "fmt" - "regexp" - "strconv" - "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/clients/dclient" engineutils "github.com/kyverno/kyverno/pkg/engine/utils" "github.com/kyverno/kyverno/pkg/logging" - wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard" + datautils "github.com/kyverno/kyverno/pkg/utils/data" "github.com/pkg/errors" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" @@ -19,97 +15,8 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/discovery" ) -var regexVersion = regexp.MustCompile(`v(\d+).(\d+).(\d+)\.*`) - -// CopyMap creates a full copy of the target map -func CopyMap(m map[string]interface{}) map[string]interface{} { - mapCopy := make(map[string]interface{}) - for k, v := range m { - mapCopy[k] = v - } - - return mapCopy -} - -// CopySliceOfMaps creates a full copy of the target slice -func CopySliceOfMaps(s []map[string]interface{}) []interface{} { - sliceCopy := make([]interface{}, len(s)) - for i, v := range s { - sliceCopy[i] = CopyMap(v) - } - - return sliceCopy -} - -func ToMap(data interface{}) (map[string]interface{}, error) { - if m, ok := data.(map[string]interface{}); ok { - return m, nil - } - - b, err := json.Marshal(data) - if err != nil { - return nil, err - } - - mapData := make(map[string]interface{}) - err = json.Unmarshal(b, &mapData) - if err != nil { - return nil, err - } - - return mapData, nil -} - -// Contains checks if a string is contained in a list of string -func contains(list []string, element string, fn func(string, string) bool) bool { - for _, e := range list { - if fn(e, element) { - return true - } - } - return false -} - -// ContainsNamepace check if namespace satisfies any list of pattern(regex) -func ContainsNamepace(patterns []string, ns string) bool { - return contains(patterns, ns, comparePatterns) -} - -func ContainsWildcardPatterns(patterns []string, key string) bool { - return contains(patterns, key, comparePatterns) -} - -func comparePatterns(pattern, ns string) bool { - return wildcard.Match(pattern, ns) -} - -// CRDsInstalled checks if the Kyverno CRDs are installed or not -func CRDsInstalled(discovery dclient.IDiscovery) bool { - kyvernoCRDs := []string{"ClusterPolicy", "ClusterPolicyReport", "PolicyReport", "AdmissionReport", "BackgroundScanReport", "ClusterAdmissionReport", "ClusterBackgroundScanReport"} - for _, crd := range kyvernoCRDs { - if !isCRDInstalled(discovery, crd) { - return false - } - } - - return true -} - -func isCRDInstalled(discoveryClient dclient.IDiscovery, kind string) bool { - gvr, err := discoveryClient.GetGVRFromKind(kind) - if gvr.Empty() { - if err == nil { - err = fmt.Errorf("not found") - } - logging.Error(err, "failed to retrieve CRD", "kind", kind) - return false - } - return true -} - // ExtractResources extracts the new and old resource as unstructured func ExtractResources(newRaw []byte, request *admissionv1.AdmissionRequest) (unstructured.Unstructured, unstructured.Unstructured, error) { var emptyResource unstructured.Unstructured @@ -241,7 +148,7 @@ func RedactSecret(resource *unstructured.Unstructured) (unstructured.Unstructure } } if secret.Annotations != nil { - metadata, err := ToMap(resource.Object["metadata"]) + metadata, err := datautils.ToMap(resource.Object["metadata"]) if err != nil { return *resource, errors.Wrap(err, "unable to convert metadata to map") } @@ -257,68 +164,6 @@ func RedactSecret(resource *unstructured.Unstructured) (unstructured.Unstructure return *resource, nil } -// HigherThanKubernetesVersion compare Kubernetes client version to user given version -func HigherThanKubernetesVersion(client discovery.ServerVersionInterface, log logr.Logger, major, minor, patch int) bool { - logger := log.WithName("CompareKubernetesVersion") - serverVersion, err := client.ServerVersion() - if err != nil { - logger.Error(err, "Failed to get kubernetes server version") - return false - } - - b, err := isVersionHigher(serverVersion.String(), major, minor, patch) - if err != nil { - logger.Error(err, "serverVersion", serverVersion.String()) - return false - } - - return b -} - -func isVersionHigher(version string, major int, minor int, patch int) (bool, error) { - groups := regexVersion.FindStringSubmatch(version) - if len(groups) != 4 { - return false, fmt.Errorf("invalid version %s. Expected {major}.{minor}.{patch}", version) - } - - currentMajor, err := strconv.Atoi(groups[1]) - if err != nil { - return false, fmt.Errorf("failed to extract major version from %s", version) - } - - currentMinor, err := strconv.Atoi(groups[2]) - if err != nil { - return false, fmt.Errorf("failed to extract minor version from %s", version) - } - - currentPatch, err := strconv.Atoi(groups[3]) - if err != nil { - return false, fmt.Errorf("failed to extract minor version from %s", version) - } - - if currentMajor < major || - (currentMajor == major && currentMinor < minor) || - (currentMajor == major && currentMinor == minor && currentPatch <= patch) { - return false, nil - } - - return true, nil -} - -// SliceContains checks whether values are contained in slice -func SliceContains(slice []string, values ...string) bool { - sliceElementsMap := make(map[string]bool, len(slice)) - for _, sliceElement := range slice { - sliceElementsMap[sliceElement] = true - } - for _, value := range values { - if sliceElementsMap[value] { - return true - } - } - return false -} - // ApiextensionsJsonToKyvernoConditions takes in user-provided conditions in abstract apiextensions.JSON form // and converts it into []kyverno.Condition or kyverno.AnyAllConditions according to its content. // it also helps in validating the condtions as it returns an error when the conditions are provided wrongfully by the user. @@ -394,38 +239,3 @@ func OverrideRuntimeErrorHandler() { } } } - -func SeperateWildcards(l []string) (lw []string, rl []string) { - for _, val := range l { - if wildcard.ContainsWildcard(val) { - lw = append(lw, val) - } else { - rl = append(rl, val) - } - } - return lw, rl -} - -func CheckWildcardNamespaces(patterns []string, ns []string) (string, string, bool) { - for _, n := range ns { - pat, element, boolval := containsNamespaceWithStringReturn(patterns, n) - if boolval { - return pat, element, true - } - } - return "", "", false -} - -func containsWithStringReturn(list []string, element string, fn func(string, string) bool) (string, string, bool) { - for _, e := range list { - if fn(e, element) { - return e, element, true - } - } - return "", "", false -} - -// containsNamespaceWithStringReturn check if namespace satisfies any list of pattern(regex) -func containsNamespaceWithStringReturn(patterns []string, ns string) (string, string, bool) { - return containsWithStringReturn(patterns, ns, comparePatterns) -} diff --git a/pkg/utils/util_test.go b/pkg/utils/util_test.go index 7adbaf0820..1a2aa8e5bf 100644 --- a/pkg/utils/util_test.go +++ b/pkg/utils/util_test.go @@ -6,84 +6,6 @@ import ( "gotest.tools/assert" ) -func Test_OriginalMapMustNotBeChanged(t *testing.T) { - // no variables - originalMap := map[string]interface{}{ - "rsc": 3711, - "r": 2138, - "gri": 1908, - "adg": 912, - } - - mapCopy := CopyMap(originalMap) - mapCopy["r"] = 1 - - assert.Equal(t, originalMap["r"], 2138) -} - -func Test_containsNs(t *testing.T) { - var patterns []string - var res bool - patterns = []string{"*"} - res = ContainsNamepace(patterns, "default") - assert.Assert(t, res == true) - - patterns = []string{"*", "default"} - res = ContainsNamepace(patterns, "default") - assert.Assert(t, res == true) - - patterns = []string{"default2", "default"} - res = ContainsNamepace(patterns, "default1") - assert.Assert(t, res == false) - - patterns = []string{"d*"} - res = ContainsNamepace(patterns, "default") - assert.Assert(t, res == true) - - patterns = []string{"d*"} - res = ContainsNamepace(patterns, "test") - assert.Assert(t, res == false) - - patterns = []string{} - res = ContainsNamepace(patterns, "test") - assert.Assert(t, res == false) -} - -func Test_higherVersion(t *testing.T) { - v, err := isVersionHigher("invalid.version", 1, 1, 1) - assert.Assert(t, v == false && err != nil) - - v, err = isVersionHigher("invalid-version", 0, 0, 0) - assert.Assert(t, v == false && err != nil) - - v, err = isVersionHigher("v1.1.1", 1, 1, 1) - assert.Assert(t, v == false && err == nil) - - v, err = isVersionHigher("v1.0.0", 1, 1, 1) - assert.Assert(t, v == false && err == nil) - - v, err = isVersionHigher("v1.5.9", 1, 5, 8) - assert.Assert(t, v == true && err == nil) - - v, err = isVersionHigher("v1.5.9+distro", 1, 5, 8) - assert.Assert(t, v == true && err == nil) - - v, err = isVersionHigher("v1.5.9+distro", 1, 5, 8) - assert.Assert(t, v == true && err == nil) - - v, err = isVersionHigher("v1.5.9-rc2", 1, 5, 9) - assert.Assert(t, v == false && err == nil) - - v, err = isVersionHigher("v1.5.9", 2, 1, 0) - assert.Assert(t, v == false && err == nil) - - v, err = isVersionHigher("v2.1.0", 1, 5, 9) - assert.Assert(t, v == true && err == nil) - - v, err = isVersionHigher("v1.5.9-x-v1.5.9.x", 1, 5, 8) - assert.Assert(t, v == true && err == nil) -} - func Test_ConvertResource(t *testing.T) { testCases := []struct { name string @@ -155,181 +77,3 @@ func Test_ConvertResource(t *testing.T) { break } } - -func Test_SeperateWildcards(t *testing.T) { - testcases := []struct { - description string - inputList []string - expList1 []string - expList2 []string - }{ - { - description: "tc1", - inputList: []string{"test*", "default", "default1", "hello"}, - expList1: []string{"test*"}, - expList2: []string{"default", "default1", "hello"}, - }, - { - description: "tc2", - inputList: []string{"test*", "default*", "default1?", "hello?"}, - expList1: []string{"test*", "default*", "default1?", "hello?"}, - expList2: nil, - }, - { - description: "tc3", - inputList: []string{"test", "default", "default1", "hello"}, - expList1: nil, - expList2: []string{"test", "default", "default1", "hello"}, - }, - { - description: "tc4", - inputList: nil, - expList1: nil, - expList2: nil, - }, - } - for _, tc := range testcases { - t.Run(tc.description, func(t *testing.T) { - list1, list2 := SeperateWildcards(tc.inputList) - assert.DeepEqual(t, list1, tc.expList1) - assert.DeepEqual(t, list2, tc.expList2) - }) - } -} - -func Test_CheckWildcardNamespaces(t *testing.T) { - testcases := []struct { - description string - inputPatterns []string - inputNs []string - expString1 string - expString2 string - expBool bool - }{ - { - description: "tc1", - inputPatterns: []string{"default*", "test*"}, - inputNs: []string{"default", "default1"}, - expString1: "default*", - expString2: "default", - expBool: true, - }, - { - description: "tc2", - inputPatterns: []string{"test*"}, - inputNs: []string{"default1", "test"}, - expString1: "test*", - expString2: "test", - expBool: true, - }, - { - description: "tc3", - inputPatterns: []string{"*"}, - inputNs: []string{"default1", "test"}, - expString1: "*", - expString2: "default1", - expBool: true, - }, - { - description: "tc4", - inputPatterns: []string{"a*"}, - inputNs: []string{"default1", "test"}, - expString1: "", - expString2: "", - expBool: false, - }, - { - description: "tc5", - inputPatterns: nil, - inputNs: []string{"default1", "test"}, - expString1: "", - expString2: "", - expBool: false, - }, - { - description: "tc6", - inputPatterns: []string{"*"}, - inputNs: nil, - expString1: "", - expString2: "", - expBool: false, - }, - { - description: "tc7", - inputPatterns: nil, - inputNs: nil, - expString1: "", - expString2: "", - expBool: false, - }, - } - for _, tc := range testcases { - t.Run(tc.description, func(t *testing.T) { - str1, str2, actualBool := CheckWildcardNamespaces(tc.inputPatterns, tc.inputNs) - assert.Equal(t, str1, tc.expString1) - assert.Equal(t, str2, tc.expString2) - assert.Equal(t, actualBool, tc.expBool) - }) - } -} - -func Test_containsNamespaceWithStringReturn(t *testing.T) { - testcases := []struct { - description string - inputPattern []string - inputNs string - expStr1 string - expStr2 string - expBool bool - }{ - { - description: "tc1", - inputPattern: []string{"default*"}, - inputNs: "default", - expStr1: "default*", - expStr2: "default", - expBool: true, - }, - { - description: "tc2", - inputPattern: []string{"*"}, - inputNs: "default", - expStr1: "*", - expStr2: "default", - expBool: true, - }, - { - description: "tc3", - inputPattern: []string{"*"}, - inputNs: "default", - expStr1: "*", - expStr2: "default", - expBool: true, - }, - { - description: "tc4", - inputPattern: nil, - inputNs: "default", - expStr1: "", - expStr2: "", - expBool: false, - }, - { - description: "tc5", - inputPattern: nil, - inputNs: "", - expStr1: "", - expStr2: "", - expBool: false, - }, - } - - for _, tc := range testcases { - t.Run(tc.description, func(t *testing.T) { - str1, str2, actualBool := containsNamespaceWithStringReturn(tc.inputPattern, tc.inputNs) - assert.Equal(t, str1, tc.expStr1) - assert.Equal(t, str2, tc.expStr2) - assert.Equal(t, actualBool, tc.expBool) - }) - } -} diff --git a/pkg/utils/wildcard/utils.go b/pkg/utils/wildcard/utils.go index c0a3d44c73..1fd83d1377 100644 --- a/pkg/utils/wildcard/utils.go +++ b/pkg/utils/wildcard/utils.go @@ -5,3 +5,32 @@ import "strings" func ContainsWildcard(v string) bool { return strings.Contains(v, "*") || strings.Contains(v, "?") } + +// MatchPatterns check if any text satisfies any pattern +func MatchPatterns(patterns []string, names ...string) (string, string, bool) { + for _, name := range names { + for _, pattern := range patterns { + if Match(pattern, name) { + return pattern, name, true + } + } + } + return "", "", false +} + +// CheckPatterns check if any text satisfies any pattern +func CheckPatterns(patterns []string, names ...string) bool { + _, _, match := MatchPatterns(patterns, names...) + return match +} + +func SeperateWildcards(l []string) (lw []string, rl []string) { + for _, val := range l { + if ContainsWildcard(val) { + lw = append(lw, val) + } else { + rl = append(rl, val) + } + } + return lw, rl +} diff --git a/pkg/utils/wildcard/utils_test.go b/pkg/utils/wildcard/utils_test.go index 1caf90156a..c80f32a465 100644 --- a/pkg/utils/wildcard/utils_test.go +++ b/pkg/utils/wildcard/utils_test.go @@ -87,3 +87,148 @@ func TestContainsWildcard(t *testing.T) { }) } } + +func TestCheckPatterns(t *testing.T) { + var patterns []string + var res bool + patterns = []string{"*"} + res = CheckPatterns(patterns, "default") + assert.Equal(t, true, res) + + patterns = []string{"*", "default"} + res = CheckPatterns(patterns, "default") + assert.Equal(t, true, res) + + patterns = []string{"default2", "default"} + res = CheckPatterns(patterns, "default1") + assert.Equal(t, false, res) + + patterns = []string{"d*"} + res = CheckPatterns(patterns, "default") + assert.Equal(t, true, res) + + patterns = []string{"d*"} + res = CheckPatterns(patterns, "test") + assert.Equal(t, false, res) + + patterns = []string{} + res = CheckPatterns(patterns, "test") + assert.Equal(t, false, res) +} + +func Test_MatchPatterns(t *testing.T) { + testcases := []struct { + description string + inputPatterns []string + inputNs []string + expString1 string + expString2 string + expBool bool + }{ + { + description: "tc1", + inputPatterns: []string{"default*", "test*"}, + inputNs: []string{"default", "default1"}, + expString1: "default*", + expString2: "default", + expBool: true, + }, + { + description: "tc2", + inputPatterns: []string{"test*"}, + inputNs: []string{"default1", "test"}, + expString1: "test*", + expString2: "test", + expBool: true, + }, + { + description: "tc3", + inputPatterns: []string{"*"}, + inputNs: []string{"default1", "test"}, + expString1: "*", + expString2: "default1", + expBool: true, + }, + { + description: "tc4", + inputPatterns: []string{"a*"}, + inputNs: []string{"default1", "test"}, + expString1: "", + expString2: "", + expBool: false, + }, + { + description: "tc5", + inputPatterns: nil, + inputNs: []string{"default1", "test"}, + expString1: "", + expString2: "", + expBool: false, + }, + { + description: "tc6", + inputPatterns: []string{"*"}, + inputNs: nil, + expString1: "", + expString2: "", + expBool: false, + }, + { + description: "tc7", + inputPatterns: nil, + inputNs: nil, + expString1: "", + expString2: "", + expBool: false, + }, + } + for _, tc := range testcases { + t.Run(tc.description, func(t *testing.T) { + str1, str2, actualBool := MatchPatterns(tc.inputPatterns, tc.inputNs...) + assert.Equal(t, str1, tc.expString1) + assert.Equal(t, str2, tc.expString2) + assert.Equal(t, actualBool, tc.expBool) + }) + } +} + +func Test_SeperateWildcards(t *testing.T) { + testcases := []struct { + description string + inputList []string + expList1 []string + expList2 []string + }{ + { + description: "tc1", + inputList: []string{"test*", "default", "default1", "hello"}, + expList1: []string{"test*"}, + expList2: []string{"default", "default1", "hello"}, + }, + { + description: "tc2", + inputList: []string{"test*", "default*", "default1?", "hello?"}, + expList1: []string{"test*", "default*", "default1?", "hello?"}, + expList2: nil, + }, + { + description: "tc3", + inputList: []string{"test", "default", "default1", "hello"}, + expList1: nil, + expList2: []string{"test", "default", "default1", "hello"}, + }, + { + description: "tc4", + inputList: nil, + expList1: nil, + expList2: nil, + }, + } + for _, tc := range testcases { + t.Run(tc.description, func(t *testing.T) { + list1, list2 := SeperateWildcards(tc.inputList) + assert.Equal(t, tc.expList1, list1) + assert.Equal(t, tc.expList2, list2) + }) + } +} diff --git a/pkg/webhooks/handlers/dump_test.go b/pkg/webhooks/handlers/dump_test.go index bb989025c5..c8941e8d2f 100644 --- a/pkg/webhooks/handlers/dump_test.go +++ b/pkg/webhooks/handlers/dump_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/kyverno/kyverno/pkg/utils" + datautils "github.com/kyverno/kyverno/pkg/utils/data" "gotest.tools/assert" admissionv1 "k8s.io/api/admission/v1" ) @@ -144,28 +144,28 @@ func Test_RedactPayload(t *testing.T) { payload, err := newAdmissionRequestPayload(req) assert.NilError(t, err) if payload.Object.Object != nil { - data, err := utils.ToMap(payload.Object.Object["data"]) + data, err := datautils.ToMap(payload.Object.Object["data"]) assert.NilError(t, err) for _, v := range data { assert.Assert(t, v == "**REDACTED**") } - metadata, err := utils.ToMap(payload.Object.Object["metadata"]) + metadata, err := datautils.ToMap(payload.Object.Object["metadata"]) assert.NilError(t, err) - annotations, err := utils.ToMap(metadata["annotations"]) + annotations, err := datautils.ToMap(metadata["annotations"]) assert.NilError(t, err) for _, v := range annotations { assert.Assert(t, v == "**REDACTED**") } } if payload.OldObject.Object != nil { - data, err := utils.ToMap(payload.OldObject.Object["data"]) + data, err := datautils.ToMap(payload.OldObject.Object["data"]) assert.NilError(t, err) for _, v := range data { assert.Assert(t, v == "**REDACTED**") } - metadata, err := utils.ToMap(payload.OldObject.Object["metadata"]) + metadata, err := datautils.ToMap(payload.OldObject.Object["metadata"]) assert.NilError(t, err) - annotations, err := utils.ToMap(metadata["annotations"]) + annotations, err := datautils.ToMap(metadata["annotations"]) assert.NilError(t, err) for _, v := range annotations { assert.Assert(t, v == "**REDACTED**")