mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
refactor: move utils into sub packages (#5828)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
5313f0e46f
commit
baacf60156
17 changed files with 299 additions and 346 deletions
|
@ -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{}) {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
17
pkg/controllers/openapi/utils.go
Normal file
17
pkg/controllers/openapi/utils.go
Normal file
|
@ -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())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
69
pkg/utils/admission/resource.go
Normal file
69
pkg/utils/admission/resource.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
68
pkg/utils/kube/secret.go
Normal file
68
pkg/utils/kube/secret.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue