diff --git a/.gitignore b/.gitignore index 75ec3f0..600d2d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -.vscode/* \ No newline at end of file +.vscode \ No newline at end of file diff --git a/cmd/webhook/mutate.go b/cmd/webhook/mutate.go index f04a638..7b42e33 100644 --- a/cmd/webhook/mutate.go +++ b/cmd/webhook/mutate.go @@ -1,184 +1,126 @@ package main import ( + "encoding/json" + "fmt" + "io" "net/http" + "strings" + + admission "k8s.io/api/admission/v1" + core "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" ) +type result struct { + Allowed bool + Msg string + PatchOps []patchOperation +} + +type admitFunc func(request *admission.AdmissionRequest) (*result, error) + +type hook struct { + Create admitFunc + Delete admitFunc + Update admitFunc + Connect admitFunc +} + func webMutatePod(w http.ResponseWriter, r *http.Request) { //https://github.com/douglasmakey/admissioncontroller -} - -/* -var ( - codecs = serializer.NewCodecFactory(runtime.NewScheme()) -) - -func admissionReviewFromRequest(r *http.Request, deserializer runtime.Decoder) (*admission.AdmissionReview, error) { - // Validate that the incoming content type is correct. - if r.Header.Get("Content-Type") != "application/json" { - return nil, fmt.Errorf("expected application/json content-type") + podsValidation := hook{ + Create: validateCreate(), } - // Get the body data, which will be the AdmissionReview - // content for the request. - var body []byte - if r.Body != nil { - requestData, err := ioutil.ReadAll(r.Body) - if err != nil { - return nil, err + admissionHandler := &struct { + decoder runtime.Decoder + }{ + decoder: serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer(), + } + + // read request body + body, err := io.ReadAll(r.Body) + if err != nil { + tmpltError(w, http.StatusBadRequest, "No data in request body.") + return + } + + // see if request body can be decoded + var review admission.AdmissionReview + if _, _, err := admissionHandler.decoder.Decode(body, nil, &review); err != nil { + tmpltError(w, http.StatusBadRequest, "Unable to decode request body.") + return + } + + var o *result + switch review.Request.Operation { + case admission.Create: + if podsValidation.Create == nil { + tmpltError(w, http.StatusBadRequest, fmt.Sprintf("operation %s is not registered", review.Request.Operation)) + return } - body = requestData + o, _ = podsValidation.Create(review.Request) + case admission.Update: + if podsValidation.Update == nil { + tmpltError(w, http.StatusBadRequest, fmt.Sprintf("operation %s is not registered", review.Request.Operation)) + return + } + o, _ = podsValidation.Update(review.Request) + case admission.Delete: + if podsValidation.Delete == nil { + tmpltError(w, http.StatusBadRequest, fmt.Sprintf("operation %s is not registered", review.Request.Operation)) + return + } + o, _ = podsValidation.Delete(review.Request) + case admission.Connect: + if podsValidation.Connect == nil { + tmpltError(w, http.StatusBadRequest, fmt.Sprintf("operation %s is not registered", review.Request.Operation)) + return + } + o, _ = podsValidation.Connect(review.Request) } - // Decode the request body into - admissionReviewRequest := &admission.AdmissionReview{} - if _, _, err := deserializer.Decode(body, nil, admissionReviewRequest); err != nil { - return nil, err + admissionResult := admission.AdmissionReview{ + Response: &admission.AdmissionResponse{ + UID: review.Request.UID, + Allowed: o.Allowed, + Result: &meta.Status{ + Message: o.Msg, + }, + }, } - return admissionReviewRequest, nil -} - -func webMutatePod(w http.ResponseWriter, r *http.Request) { - httpAccessLog(r) - - deserializer := codecs.UniversalDeserializer() - - // Parse the AdmissionReview from the http request. - admissionReviewRequest, err := admissionReviewFromRequest(r, deserializer) - if err != nil { - msg := fmt.Sprintf("error getting admission review from request: %v", err) - log.Printf("[ERROR] %v", msg) - tmpltError(w, http.StatusBadRequest, msg) - return - } - - // Do server-side validation that we are only dealing with a pod resource. This - // should also be part of the MutatingWebhookConfiguration in the cluster, but - // we should verify here before continuing. - podResource := meta.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} - if admissionReviewRequest.Request.Resource != podResource { - msg := fmt.Sprintf("did not receive pod, got %s", admissionReviewRequest.Request.Resource.Resource) - log.Printf("[ERROR] %v", msg) - tmpltError(w, http.StatusBadRequest, msg) - return - } - - // Decode the pod from the AdmissionReview. - rawRequest := admissionReviewRequest.Request.Object.Raw - pod := core.Pod{} - if _, _, err := deserializer.Decode(rawRequest, nil, &pod); err != nil { - msg := fmt.Sprintf("error decoding raw pod: %v", err) - log.Printf("[ERROR] %v", msg) - tmpltError(w, http.StatusBadRequest, msg) - return - } - - // check to see if mutation is required by looking for a label - if !mutationRequired(&pod.ObjectMeta) { - mutationResp(w, admissionReviewRequest, &admission.AdmissionResponse{Allowed: true}) - } - - // Add sidecar - sidecarContainer := []core.Container{{ - Image: "ca-cert-server:latest", - }} - - patchBytes, _ := createPatch(&pod, sidecarContainer) - - // respond with patch - mutationResp(w, admissionReviewRequest, &admission.AdmissionResponse{ - Allowed: true, - Patch: patchBytes, - PatchType: func() *admission.PatchType { - pt := admission.PatchTypeJSONPatch - return &pt - }(), - }) -} - -// prepare response -func mutationResp(w http.ResponseWriter, aRRequest *admission.AdmissionReview, aResponse *admission.AdmissionResponse) { - var aRResponse admission.AdmissionReview - aRResponse.Response = aResponse - aRResponse.SetGroupVersionKind(aRRequest.GroupVersionKind()) - aRResponse.Response.UID = aRRequest.Request.UID - - resp, err := json.Marshal(aRResponse) - if err != nil { - msg := fmt.Sprintf("error marshalling response json: %v", err) - log.Printf("[ERROR] %v", msg) - tmpltError(w, http.StatusBadRequest, msg) - return - } - - w.Header().Set("Content-Type", "application/json") + resp, _ := json.Marshal(admissionResult) + w.WriteHeader(http.StatusOK) w.Write(resp) } -/* -// create mutation patch for resources -func createPatch(pod *core.Pod, containers []core.Container) ([]byte, error) { - var ( - patch []patchOperation - first bool - value interface{} - ) - - if len(pod.Spec.Containers) == 0 { - first = true - } - - for _, add := range containers { - value = add - path := "/spec/containers" - if first { - first = false - value = []core.Container{add} - } else { - path = path + "/-" +func validateCreate() admitFunc { + return func(r *admission.AdmissionRequest) (*result, error) { + pod, err := parsePod(r.Object.Raw) + if err != nil { + return &result{Msg: err.Error()}, nil } - patch = append(patch, patchOperation{ - Op: "add", - Path: path, - Value: value, - }) - } - return json.Marshal(patch) + for _, c := range pod.Spec.Containers { + if strings.HasSuffix(c.Image, ":latest") { + return &result{Msg: "You cannot use the tag 'latest' in a container."}, nil + } + } + + return &result{Allowed: true}, nil + } } -// Check whether the target resource needs to be mutated -func mutationRequired(metadata *meta.ObjectMeta) bool { - var ignoredNamespaces = []string{ - meta.NamespaceSystem, - meta.NamespacePublic, +func parsePod(object []byte) (*core.Pod, error) { + var pod core.Pod + if err := json.Unmarshal(object, &pod); err != nil { + return nil, err } - // skip special kubernetes system namespaces - for _, namespace := range ignoredNamespaces { - if metadata.Namespace == namespace { - log.Printf("[TRACE] Protected namespace detected (%s) skipping mutation: %s", metadata.Namespace, metadata.Name) - return false - } - } - - annotations := metadata.GetLabels() - if annotations == nil { - annotations = map[string]string{} - } - - // determine whether to perform mutation based on annotation for the target resource - var required bool - switch strings.ToLower(annotations["sidecar-injector-webhook/inject"]) { - case "yes", "y", "true", "t", "on": - required = true - default: - required = false - } - - log.Printf("[TRACE] Mutation policy for %s/%s: required:%b", metadata.Namespace, metadata.Name, required) - return required + return &pod, nil } -*/ diff --git a/cmd/webhook/patch.go b/cmd/webhook/patch.go new file mode 100644 index 0000000..cb5597b --- /dev/null +++ b/cmd/webhook/patch.go @@ -0,0 +1,59 @@ +package main + +const ( + addOperation = "add" + removeOperation = "remove" + replaceOperation = "replace" + copyOperation = "copy" + moveOperation = "move" +) + +type patchOperation struct { + Op string `json:"op"` + Path string `json:"path"` + From string `json:"from"` + Value interface{} `json:"value,omitempty"` +} + +func addPatchOperation(path string, value interface{}) patchOperation { + return patchOperation{ + Op: addOperation, + Path: path, + Value: value, + } +} + +// RemovePatchOperation returns a remove JSON patch operation. +func removePatchOperation(path string) patchOperation { + return patchOperation{ + Op: removeOperation, + Path: path, + } +} + +// ReplacePatchOperation returns a replace JSON patch operation. +func replacePatchOperation(path string, value interface{}) patchOperation { + return patchOperation{ + Op: replaceOperation, + Path: path, + Value: value, + } +} + +// CopyPatchOperation returns a copy JSON patch operation. +func copyPatchOperation(from, path string) patchOperation { + return patchOperation{ + Op: copyOperation, + Path: path, + From: from, + } +} + +// MovePatchOperation returns a move JSON patch operation. +func movePatchOperation(from, path string) patchOperation { + return patchOperation{ + Op: moveOperation, + Path: path, + From: from, + } +} diff --git a/go.mod b/go.mod index 8122d35..119e237 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,26 @@ module mutating-webhook go 1.19 -require github.com/hashicorp/logutils v1.0.0 +require ( + github.com/hashicorp/logutils v1.0.0 + k8s.io/api v0.25.2 + k8s.io/apimachinery v0.25.2 +) + +require ( + github.com/go-logr/logr v1.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) diff --git a/go.sum b/go.sum index 635dc62..dded106 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,87 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= +k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= +k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= +k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=