From baacf601564360ee7d5e648b019ca770700f72aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Tue, 3 Jan 2023 10:33:09 +0100 Subject: [PATCH] refactor: move utils into sub packages (#5828) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché Signed-off-by: Charles-Edouard Brétéché --- pkg/autogen/rule.go | 4 +- pkg/background/common/context.go | 4 +- pkg/controllers/openapi/controller.go | 3 +- pkg/controllers/openapi/utils.go | 17 ++ pkg/engine/common/utils.go | 4 +- pkg/engine/policyContext.go | 4 +- pkg/engine/validation_test.go | 4 +- pkg/policy/validate.go | 6 +- pkg/utils/admission/resource.go | 69 +++++ .../resource_test.go} | 45 +++- pkg/utils/admission/utils.go | 13 - pkg/utils/admission/utils_test.go | 49 ---- pkg/utils/api/json.go | 72 +++++- pkg/utils/kube/secret.go | 68 +++++ pkg/utils/util.go | 241 ------------------ pkg/webhooks/handlers/dump.go | 39 +-- pkg/webhooks/handlers/protect.go | 3 +- 17 files changed, 299 insertions(+), 346 deletions(-) create mode 100644 pkg/controllers/openapi/utils.go create mode 100644 pkg/utils/admission/resource.go rename pkg/utils/{util_test.go => admission/resource_test.go} (76%) delete mode 100644 pkg/utils/admission/utils.go delete mode 100644 pkg/utils/admission/utils_test.go create mode 100644 pkg/utils/kube/secret.go delete mode 100644 pkg/utils/util.go diff --git a/pkg/autogen/rule.go b/pkg/autogen/rule.go index dde811fda1..1378d5161b 100644 --- a/pkg/autogen/rule.go +++ b/pkg/autogen/rule.go @@ -6,7 +6,7 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/engine/variables" - "github.com/kyverno/kyverno/pkg/utils" + apiutils "github.com/kyverno/kyverno/pkg/utils/api" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) @@ -51,7 +51,7 @@ func createRule(rule *kyvernov1.Rule) *kyvernoRule { if !reflect.DeepEqual(rule.Validation, kyvernov1.Validation{}) { jsonFriendlyStruct.Validation = rule.Validation.DeepCopy() } - kyvernoAnyAllConditions, _ := utils.ApiextensionsJsonToKyvernoConditions(rule.GetAnyAllConditions()) + kyvernoAnyAllConditions, _ := apiutils.ApiextensionsJsonToKyvernoConditions(rule.GetAnyAllConditions()) switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) { case kyvernov1.AnyAllConditions: if !reflect.DeepEqual(typedAnyAllConditions, kyvernov1.AnyAllConditions{}) { diff --git a/pkg/background/common/context.go b/pkg/background/common/context.go index c90977472a..b7b557f46d 100644 --- a/pkg/background/common/context.go +++ b/pkg/background/common/context.go @@ -12,7 +12,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/context/resolvers" - utils "github.com/kyverno/kyverno/pkg/utils" + admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -34,7 +34,7 @@ func NewBackgroundContext(dclient dclient.Interface, ur *kyvernov1beta1.UpdateRe return nil, false, errors.Wrap(err, "failed to load request in context") } - new, old, err = utils.ExtractResources(nil, ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest) + new, old, err = admissionutils.ExtractResources(nil, ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest) if err != nil { return nil, false, errors.Wrap(err, "failed to load request in context") } diff --git a/pkg/controllers/openapi/controller.go b/pkg/controllers/openapi/controller.go index 4e545f48be..00365f776e 100644 --- a/pkg/controllers/openapi/controller.go +++ b/pkg/controllers/openapi/controller.go @@ -9,7 +9,6 @@ import ( "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/controllers" - util "github.com/kyverno/kyverno/pkg/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtimeSchema "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" @@ -106,7 +105,7 @@ func (c *controller) sync() { } func (c *controller) updateInClusterKindToAPIVersions() error { - util.OverrideRuntimeErrorHandler() + overrideRuntimeErrorHandler() _, apiResourceLists, err := discovery.ServerGroupsAndResources(c.client.Discovery().DiscoveryInterface()) if err != nil { if discovery.IsGroupDiscoveryFailedError(err) { diff --git a/pkg/controllers/openapi/utils.go b/pkg/controllers/openapi/utils.go new file mode 100644 index 0000000000..43c7a861d7 --- /dev/null +++ b/pkg/controllers/openapi/utils.go @@ -0,0 +1,17 @@ +package openapi + +import "k8s.io/apimachinery/pkg/util/runtime" + +func overrideRuntimeErrorHandler() { + if len(runtime.ErrorHandlers) > 0 { + runtime.ErrorHandlers[0] = func(err error) { + logger.V(6).Info("runtime error", "msg", err.Error()) + } + } else { + runtime.ErrorHandlers = []func(err error){ + func(err error) { + logger.V(6).Info("runtime error", "msg", err.Error()) + }, + } + } +} diff --git a/pkg/engine/common/utils.go b/pkg/engine/common/utils.go index 86fda385de..816ae4d81e 100644 --- a/pkg/engine/common/utils.go +++ b/pkg/engine/common/utils.go @@ -4,7 +4,7 @@ import ( "fmt" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/utils" + apiutils "github.com/kyverno/kyverno/pkg/utils/api" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" ) @@ -23,7 +23,7 @@ func GetRawKeyIfWrappedWithAttributes(str string) string { func TransformConditions(original apiextensions.JSON) (interface{}, error) { // conditions are currently in the form of []interface{} - oldConditions, err := utils.ApiextensionsJsonToKyvernoConditions(original) + oldConditions, err := apiutils.ApiextensionsJsonToKyvernoConditions(original) if err != nil { return nil, err } diff --git a/pkg/engine/policyContext.go b/pkg/engine/policyContext.go index fb9f74f848..5caa4fc3f8 100644 --- a/pkg/engine/policyContext.go +++ b/pkg/engine/policyContext.go @@ -9,7 +9,7 @@ import ( "github.com/kyverno/kyverno/pkg/config" enginectx "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/context/resolvers" - "github.com/kyverno/kyverno/pkg/utils" + admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" "github.com/pkg/errors" admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -255,7 +255,7 @@ func NewPolicyContextFromAdmissionRequest( if err != nil { return nil, errors.Wrap(err, "failed to create policy rule context") } - newResource, oldResource, err := utils.ExtractResources(nil, request) + newResource, oldResource, err := admissionutils.ExtractResources(nil, request) if err != nil { return nil, errors.Wrap(err, "failed to parse resource") } diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index b220a32099..3c91106c14 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -13,7 +13,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/utils" "github.com/kyverno/kyverno/pkg/registryclient" - utils2 "github.com/kyverno/kyverno/pkg/utils" + admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" "gotest.tools/assert" admissionv1 "k8s.io/api/admission/v1" ) @@ -2136,7 +2136,7 @@ func executeTest(t *testing.T, test testCase) { t.Fatal(err) } - newR, oldR, err := utils2.ExtractResources(nil, request) + newR, oldR, err := admissionutils.ExtractResources(nil, request) if err != nil { t.Fatal(err) } diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index 638e21ac93..0b7fb146f3 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -22,7 +22,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/openapi" - "github.com/kyverno/kyverno/pkg/utils" + apiutils "github.com/kyverno/kyverno/pkg/utils/api" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "github.com/kyverno/kyverno/pkg/utils/wildcard" "github.com/pkg/errors" @@ -898,7 +898,7 @@ func validateConditions(conditions apiextensions.JSON, schemaKey string) (string } // conditions are currently in the form of []interface{} - kyvernoConditions, err := utils.ApiextensionsJsonToKyvernoConditions(conditions) + kyvernoConditions, err := apiutils.ApiextensionsJsonToKyvernoConditions(conditions) if err != nil { return schemaKey, err } @@ -1224,7 +1224,7 @@ func validateWildcard(kinds []string, spec *kyvernov1.Spec, rule kyvernov1.Rule) } if rule.Validation.Deny != nil { - kyvernoConditions, _ := utils.ApiextensionsJsonToKyvernoConditions(rule.Validation.Deny.GetAnyAllConditions()) + kyvernoConditions, _ := apiutils.ApiextensionsJsonToKyvernoConditions(rule.Validation.Deny.GetAnyAllConditions()) switch typedConditions := kyvernoConditions.(type) { case []kyvernov1.Condition: // backwards compatibility for _, condition := range typedConditions { diff --git a/pkg/utils/admission/resource.go b/pkg/utils/admission/resource.go new file mode 100644 index 0000000000..207f6031f8 --- /dev/null +++ b/pkg/utils/admission/resource.go @@ -0,0 +1,69 @@ +package admission + +import ( + "fmt" + + engineutils "github.com/kyverno/kyverno/pkg/engine/utils" + admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func GetResourceName(request *admissionv1.AdmissionRequest) string { + resourceName := request.Kind.Kind + "/" + request.Name + if request.Namespace != "" { + resourceName = request.Namespace + "/" + resourceName + } + return resourceName +} + +// 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 + var newResource unstructured.Unstructured + var oldResource unstructured.Unstructured + var err error + + // New Resource + if newRaw == nil { + newRaw = request.Object.Raw + } + + if newRaw != nil { + newResource, err = ConvertResource(newRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace) + if err != nil { + return emptyResource, emptyResource, fmt.Errorf("failed to convert new raw to unstructured: %v", err) + } + } + + // Old Resource + oldRaw := request.OldObject.Raw + if oldRaw != nil { + oldResource, err = ConvertResource(oldRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace) + if err != nil { + return emptyResource, emptyResource, fmt.Errorf("failed to convert old raw to unstructured: %v", err) + } + } + + return newResource, oldResource, err +} + +// ConvertResource converts raw bytes to an unstructured object +func ConvertResource(raw []byte, group, version, kind, namespace string) (unstructured.Unstructured, error) { + obj, err := engineutils.ConvertToUnstructured(raw) + if err != nil { + return unstructured.Unstructured{}, fmt.Errorf("failed to convert raw to unstructured: %v", err) + } + + obj.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: version, Kind: kind}) + + if namespace != "" && kind != "Namespace" { + obj.SetNamespace(namespace) + } + + if obj.GetKind() == "Namespace" && obj.GetNamespace() != "" { + obj.SetNamespace("") + } + + return *obj, nil +} diff --git a/pkg/utils/util_test.go b/pkg/utils/admission/resource_test.go similarity index 76% rename from pkg/utils/util_test.go rename to pkg/utils/admission/resource_test.go index 1a2aa8e5bf..05ee4cb76d 100644 --- a/pkg/utils/util_test.go +++ b/pkg/utils/admission/resource_test.go @@ -1,11 +1,54 @@ -package utils +package admission import ( "testing" "gotest.tools/assert" + admissionv1 "k8s.io/api/admission/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func TestGetResourceName(t *testing.T) { + type args struct { + request *admissionv1.AdmissionRequest + } + tests := []struct { + name string + args args + want string + }{{ + name: "with namespace", + args: args{ + request: &admissionv1.AdmissionRequest{ + Kind: v1.GroupVersionKind{ + Kind: "Pod", + }, + Name: "dummy", + Namespace: "ns", + }, + }, + want: "ns/Pod/dummy", + }, { + name: "without namespace", + args: args{ + request: &admissionv1.AdmissionRequest{ + Kind: v1.GroupVersionKind{ + Kind: "Namespace", + }, + Name: "dummy", + }, + }, + want: "Namespace/dummy", + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetResourceName(tt.args.request); got != tt.want { + t.Errorf("GetResourceName() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_ConvertResource(t *testing.T) { testCases := []struct { name string diff --git a/pkg/utils/admission/utils.go b/pkg/utils/admission/utils.go deleted file mode 100644 index a8e5f29cbc..0000000000 --- a/pkg/utils/admission/utils.go +++ /dev/null @@ -1,13 +0,0 @@ -package admission - -import ( - admissionv1 "k8s.io/api/admission/v1" -) - -func GetResourceName(request *admissionv1.AdmissionRequest) string { - resourceName := request.Kind.Kind + "/" + request.Name - if request.Namespace != "" { - resourceName = request.Namespace + "/" + resourceName - } - return resourceName -} diff --git a/pkg/utils/admission/utils_test.go b/pkg/utils/admission/utils_test.go deleted file mode 100644 index 34cd1d200a..0000000000 --- a/pkg/utils/admission/utils_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package admission - -import ( - "testing" - - admissionv1 "k8s.io/api/admission/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestGetResourceName(t *testing.T) { - type args struct { - request *admissionv1.AdmissionRequest - } - tests := []struct { - name string - args args - want string - }{{ - name: "with namespace", - args: args{ - request: &admissionv1.AdmissionRequest{ - Kind: v1.GroupVersionKind{ - Kind: "Pod", - }, - Name: "dummy", - Namespace: "ns", - }, - }, - want: "ns/Pod/dummy", - }, { - name: "without namespace", - args: args{ - request: &admissionv1.AdmissionRequest{ - Kind: v1.GroupVersionKind{ - Kind: "Namespace", - }, - Name: "dummy", - }, - }, - want: "Namespace/dummy", - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetResourceName(tt.args.request); got != tt.want { - t.Errorf("GetResourceName() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/utils/api/json.go b/pkg/utils/api/json.go index c1d9ab3acb..3f3d3f9703 100644 --- a/pkg/utils/api/json.go +++ b/pkg/utils/api/json.go @@ -2,25 +2,85 @@ package api import ( "encoding/json" + "fmt" + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" ) // Deserialize "apiextensions.JSON" to a typed array -func DeserializeJSONArray[T any](j apiextensions.JSON) ([]T, error) { - if j == nil { +func DeserializeJSONArray[T any](in apiextensions.JSON) ([]T, error) { + if in == nil { return nil, nil } - - data, err := json.Marshal(j) + data, err := json.Marshal(in) if err != nil { return nil, err } - var res []T if err := json.Unmarshal(data, &res); err != nil { return nil, err } - return res, nil } + +// 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. +func ApiextensionsJsonToKyvernoConditions(in apiextensions.JSON) (interface{}, error) { + path := "preconditions/validate.deny.conditions" + + // checks for the existence any other field apart from 'any'/'all' under preconditions/validate.deny.conditions + unknownFieldChecker := func(jsonByteArr []byte, path string) error { + allowedKeys := map[string]bool{ + "any": true, + "all": true, + } + var jsonDecoded map[string]interface{} + if err := json.Unmarshal(jsonByteArr, &jsonDecoded); err != nil { + return fmt.Errorf("error occurred while checking for unknown fields under %s: %+v", path, err) + } + for k := range jsonDecoded { + if !allowedKeys[k] { + return fmt.Errorf("unknown field '%s' found under %s", k, path) + } + } + return nil + } + + // marshalling the abstract apiextensions.JSON back to JSON form + jsonByte, err := json.Marshal(in) + if err != nil { + return nil, fmt.Errorf("error occurred while marshalling %s: %+v", path, err) + } + + var kyvernoOldConditions []kyvernov1.Condition + if err = json.Unmarshal(jsonByte, &kyvernoOldConditions); err == nil { + var validConditionOperator bool + + for _, jsonOp := range kyvernoOldConditions { + for _, validOp := range kyvernov1.ConditionOperators { + if jsonOp.Operator == validOp { + validConditionOperator = true + } + } + if !validConditionOperator { + return nil, fmt.Errorf("invalid condition operator: %s", jsonOp.Operator) + } + validConditionOperator = false + } + + return kyvernoOldConditions, nil + } + + var kyvernoAnyAllConditions kyvernov1.AnyAllConditions + if err = json.Unmarshal(jsonByte, &kyvernoAnyAllConditions); err == nil { + // checking if unknown fields exist or not + err = unknownFieldChecker(jsonByte, path) + if err != nil { + return nil, fmt.Errorf("error occurred while parsing %s: %+v", path, err) + } + return kyvernoAnyAllConditions, nil + } + return nil, fmt.Errorf("error occurred while parsing %s: %+v", path, err) +} diff --git a/pkg/utils/kube/secret.go b/pkg/utils/kube/secret.go new file mode 100644 index 0000000000..e1f548be73 --- /dev/null +++ b/pkg/utils/kube/secret.go @@ -0,0 +1,68 @@ +package kube + +import ( + "encoding/json" + + datautils "github.com/kyverno/kyverno/pkg/utils/data" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// RedactSecret masks keys of data and metadata.annotation fields of Secrets. +func RedactSecret(resource *unstructured.Unstructured) (unstructured.Unstructured, error) { + var secret *corev1.Secret + data, err := json.Marshal(resource.Object) + if err != nil { + return *resource, err + } + err = json.Unmarshal(data, &secret) + if err != nil { + return *resource, errors.Wrap(err, "unable to convert object to secret") + } + stringSecret := struct { + Data map[string]string `json:"string_data"` + *corev1.Secret + }{ + Data: make(map[string]string), + Secret: secret, + } + for key := range secret.Data { + secret.Data[key] = []byte("**REDACTED**") + stringSecret.Data[key] = string(secret.Data[key]) + } + for key := range secret.Annotations { + secret.Annotations[key] = "**REDACTED**" + } + updateSecret := map[string]interface{}{} + raw, err := json.Marshal(stringSecret) + if err != nil { + return *resource, nil + } + err = json.Unmarshal(raw, &updateSecret) + if err != nil { + return *resource, errors.Wrap(err, "unable to convert object from secret") + } + if secret.Data != nil { + v := updateSecret["string_data"].(map[string]interface{}) + err = unstructured.SetNestedMap(resource.Object, v, "data") + if err != nil { + return *resource, errors.Wrap(err, "failed to set secret.data") + } + } + if secret.Annotations != nil { + metadata, err := datautils.ToMap(resource.Object["metadata"]) + if err != nil { + return *resource, errors.Wrap(err, "unable to convert metadata to map") + } + updatedMeta := updateSecret["metadata"].(map[string]interface{}) + if err != nil { + return *resource, errors.Wrap(err, "unable to convert object from secret") + } + err = unstructured.SetNestedMap(metadata, updatedMeta["annotations"].(map[string]interface{}), "annotations") + if err != nil { + return *resource, errors.Wrap(err, "failed to set secret.annotations") + } + } + return *resource, nil +} diff --git a/pkg/utils/util.go b/pkg/utils/util.go deleted file mode 100644 index 6729f42440..0000000000 --- a/pkg/utils/util.go +++ /dev/null @@ -1,241 +0,0 @@ -package utils - -import ( - "encoding/json" - "fmt" - - kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - engineutils "github.com/kyverno/kyverno/pkg/engine/utils" - "github.com/kyverno/kyverno/pkg/logging" - datautils "github.com/kyverno/kyverno/pkg/utils/data" - "github.com/pkg/errors" - admissionv1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/runtime" -) - -// 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 - var newResource unstructured.Unstructured - var oldResource unstructured.Unstructured - var err error - - // New Resource - if newRaw == nil { - newRaw = request.Object.Raw - } - - if newRaw != nil { - newResource, err = ConvertResource(newRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace) - if err != nil { - return emptyResource, emptyResource, fmt.Errorf("failed to convert new raw to unstructured: %v", err) - } - } - - // Old Resource - oldRaw := request.OldObject.Raw - if oldRaw != nil { - oldResource, err = ConvertResource(oldRaw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace) - if err != nil { - return emptyResource, emptyResource, fmt.Errorf("failed to convert old raw to unstructured: %v", err) - } - } - - return newResource, oldResource, err -} - -// ConvertResource converts raw bytes to an unstructured object -func ConvertResource(raw []byte, group, version, kind, namespace string) (unstructured.Unstructured, error) { - obj, err := engineutils.ConvertToUnstructured(raw) - if err != nil { - return unstructured.Unstructured{}, fmt.Errorf("failed to convert raw to unstructured: %v", err) - } - - obj.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: version, Kind: kind}) - - if namespace != "" && kind != "Namespace" { - obj.SetNamespace(namespace) - } - - if obj.GetKind() == "Namespace" && obj.GetNamespace() != "" { - obj.SetNamespace("") - } - - return *obj, nil -} - -func NormalizeSecret(resource *unstructured.Unstructured) (unstructured.Unstructured, error) { - var secret corev1.Secret - data, err := json.Marshal(resource.Object) - if err != nil { - return *resource, err - } - err = json.Unmarshal(data, &secret) - if err != nil { - return *resource, errors.Wrap(err, "object unable to convert to secret") - } - for k, v := range secret.Data { - if len(v) == 0 { - secret.Data[k] = []byte("") - } - } - updateSecret := map[string]interface{}{} - raw, err := json.Marshal(&secret) - if err != nil { - return *resource, nil - } - - err = json.Unmarshal(raw, &updateSecret) - if err != nil { - return *resource, nil - } - - if err != nil { - return *resource, errors.Wrap(err, "object unable to convert from secret") - } - if secret.Data != nil { - err = unstructured.SetNestedMap(resource.Object, updateSecret["data"].(map[string]interface{}), "data") - if err != nil { - return *resource, errors.Wrap(err, "failed to set secret.data") - } - } - return *resource, nil -} - -// RedactSecret masks keys of data and metadata.annotation fields of Secrets. -func RedactSecret(resource *unstructured.Unstructured) (unstructured.Unstructured, error) { - var secret *corev1.Secret - data, err := json.Marshal(resource.Object) - if err != nil { - return *resource, err - } - err = json.Unmarshal(data, &secret) - if err != nil { - return *resource, errors.Wrap(err, "unable to convert object to secret") - } - stringSecret := struct { - Data map[string]string `json:"string_data"` - *corev1.Secret - }{ - Data: make(map[string]string), - Secret: secret, - } - for key := range secret.Data { - secret.Data[key] = []byte("**REDACTED**") - stringSecret.Data[key] = string(secret.Data[key]) - } - for key := range secret.Annotations { - secret.Annotations[key] = "**REDACTED**" - } - updateSecret := map[string]interface{}{} - raw, err := json.Marshal(stringSecret) - if err != nil { - return *resource, nil - } - err = json.Unmarshal(raw, &updateSecret) - if err != nil { - return *resource, errors.Wrap(err, "unable to convert object from secret") - } - if secret.Data != nil { - v := updateSecret["string_data"].(map[string]interface{}) - err = unstructured.SetNestedMap(resource.Object, v, "data") - if err != nil { - return *resource, errors.Wrap(err, "failed to set secret.data") - } - } - if secret.Annotations != nil { - metadata, err := datautils.ToMap(resource.Object["metadata"]) - if err != nil { - return *resource, errors.Wrap(err, "unable to convert metadata to map") - } - updatedMeta := updateSecret["metadata"].(map[string]interface{}) - if err != nil { - return *resource, errors.Wrap(err, "unable to convert object from secret") - } - err = unstructured.SetNestedMap(metadata, updatedMeta["annotations"].(map[string]interface{}), "annotations") - if err != nil { - return *resource, errors.Wrap(err, "failed to set secret.annotations") - } - } - return *resource, nil -} - -// 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. -func ApiextensionsJsonToKyvernoConditions(original apiextensions.JSON) (interface{}, error) { - path := "preconditions/validate.deny.conditions" - - // checks for the existence any other field apart from 'any'/'all' under preconditions/validate.deny.conditions - unknownFieldChecker := func(jsonByteArr []byte, path string) error { - allowedKeys := map[string]bool{ - "any": true, - "all": true, - } - var jsonDecoded map[string]interface{} - if err := json.Unmarshal(jsonByteArr, &jsonDecoded); err != nil { - return fmt.Errorf("error occurred while checking for unknown fields under %s: %+v", path, err) - } - for k := range jsonDecoded { - if !allowedKeys[k] { - return fmt.Errorf("unknown field '%s' found under %s", k, path) - } - } - return nil - } - - // marshalling the abstract apiextensions.JSON back to JSON form - jsonByte, err := json.Marshal(original) - if err != nil { - return nil, fmt.Errorf("error occurred while marshalling %s: %+v", path, err) - } - - var kyvernoOldConditions []kyvernov1.Condition - if err = json.Unmarshal(jsonByte, &kyvernoOldConditions); err == nil { - var validConditionOperator bool - - for _, jsonOp := range kyvernoOldConditions { - for _, validOp := range kyvernov1.ConditionOperators { - if jsonOp.Operator == validOp { - validConditionOperator = true - } - } - if !validConditionOperator { - return nil, fmt.Errorf("invalid condition operator: %s", jsonOp.Operator) - } - validConditionOperator = false - } - - return kyvernoOldConditions, nil - } - - var kyvernoAnyAllConditions kyvernov1.AnyAllConditions - if err = json.Unmarshal(jsonByte, &kyvernoAnyAllConditions); err == nil { - // checking if unknown fields exist or not - err = unknownFieldChecker(jsonByte, path) - if err != nil { - return nil, fmt.Errorf("error occurred while parsing %s: %+v", path, err) - } - return kyvernoAnyAllConditions, nil - } - return nil, fmt.Errorf("error occurred while parsing %s: %+v", path, err) -} - -func OverrideRuntimeErrorHandler() { - logger := logging.WithName("RuntimeErrorHandler") - if len(runtime.ErrorHandlers) > 0 { - runtime.ErrorHandlers[0] = func(err error) { - logger.V(6).Info("runtime error", "msg", err.Error()) - } - } else { - runtime.ErrorHandlers = []func(err error){ - func(err error) { - logger.V(6).Info("runtime error", "msg", err.Error()) - }, - } - } -} diff --git a/pkg/webhooks/handlers/dump.go b/pkg/webhooks/handlers/dump.go index 308936c2ae..7391d2724c 100644 --- a/pkg/webhooks/handlers/dump.go +++ b/pkg/webhooks/handlers/dump.go @@ -7,7 +7,8 @@ import ( "github.com/go-logr/logr" engineutils "github.com/kyverno/kyverno/pkg/engine/utils" - "github.com/kyverno/kyverno/pkg/utils" + admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" + kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" admissionv1 "k8s.io/api/admission/v1" authenticationv1 "k8s.io/api/authentication/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -58,33 +59,33 @@ type admissionRequestPayload struct { Options unstructured.Unstructured `json:"options,omitempty"` } -func newAdmissionRequestPayload(rq *admissionv1.AdmissionRequest) (*admissionRequestPayload, error) { - newResource, oldResource, err := utils.ExtractResources(nil, rq) +func newAdmissionRequestPayload(request *admissionv1.AdmissionRequest) (*admissionRequestPayload, error) { + newResource, oldResource, err := admissionutils.ExtractResources(nil, request) if err != nil { return nil, err } options := new(unstructured.Unstructured) - if rq.Options.Raw != nil { - options, err = engineutils.ConvertToUnstructured(rq.Options.Raw) + if request.Options.Raw != nil { + options, err = engineutils.ConvertToUnstructured(request.Options.Raw) if err != nil { return nil, err } } return redactPayload(&admissionRequestPayload{ - UID: rq.UID, - Kind: rq.Kind, - Resource: rq.Resource, - SubResource: rq.SubResource, - RequestKind: rq.RequestKind, - RequestResource: rq.RequestResource, - RequestSubResource: rq.RequestSubResource, - Name: rq.Name, - Namespace: rq.Namespace, - Operation: string(rq.Operation), - UserInfo: rq.UserInfo, + UID: request.UID, + Kind: request.Kind, + Resource: request.Resource, + SubResource: request.SubResource, + RequestKind: request.RequestKind, + RequestResource: request.RequestResource, + RequestSubResource: request.RequestSubResource, + Name: request.Name, + Namespace: request.Namespace, + Operation: string(request.Operation), + UserInfo: request.UserInfo, Object: newResource, OldObject: oldResource, - DryRun: rq.DryRun, + DryRun: request.DryRun, Options: *options, }) } @@ -92,14 +93,14 @@ func newAdmissionRequestPayload(rq *admissionv1.AdmissionRequest) (*admissionReq func redactPayload(payload *admissionRequestPayload) (*admissionRequestPayload, error) { if strings.EqualFold(payload.Kind.Kind, "Secret") { if payload.Object.Object != nil { - obj, err := utils.RedactSecret(&payload.Object) + obj, err := kubeutils.RedactSecret(&payload.Object) if err != nil { return nil, err } payload.Object = obj } if payload.OldObject.Object != nil { - oldObj, err := utils.RedactSecret(&payload.OldObject) + oldObj, err := kubeutils.RedactSecret(&payload.OldObject) if err != nil { return nil, err } diff --git a/pkg/webhooks/handlers/protect.go b/pkg/webhooks/handlers/protect.go index 69cc971d48..2a747d26fe 100644 --- a/pkg/webhooks/handlers/protect.go +++ b/pkg/webhooks/handlers/protect.go @@ -9,7 +9,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" admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -24,7 +23,7 @@ func (inner AdmissionHandler) WithProtection(enabled bool) AdmissionHandler { func (inner AdmissionHandler) withProtection() AdmissionHandler { return func(ctx context.Context, logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse { - newResource, oldResource, err := utils.ExtractResources(nil, request) + newResource, oldResource, err := admissionutils.ExtractResources(nil, request) if err != nil { logger.Error(err, "Failed to extract resources") return admissionutils.Response(request.UID, err)